diff options
Diffstat (limited to 'mailnews/local/src')
45 files changed, 22527 insertions, 0 deletions
diff --git a/mailnews/local/src/moz.build b/mailnews/local/src/moz.build new file mode 100644 index 000000000..5f999ce46 --- /dev/null +++ b/mailnews/local/src/moz.build @@ -0,0 +1,36 @@ +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'nsLocalMailFolder.cpp', + 'nsLocalUndoTxn.cpp', + 'nsLocalUtils.cpp', + 'nsMailboxProtocol.cpp', + 'nsMailboxServer.cpp', + 'nsMailboxService.cpp', + 'nsMailboxUrl.cpp', + 'nsMsgBrkMBoxStore.cpp', + 'nsMsgLocalStoreUtils.cpp', + 'nsMsgMaildirStore.cpp', + 'nsNoIncomingServer.cpp', + 'nsNoneService.cpp', + 'nsParseMailbox.cpp', + 'nsPop3IncomingServer.cpp', + 'nsPop3Protocol.cpp', + 'nsPop3Service.cpp', + 'nsPop3Sink.cpp', + 'nsPop3URL.cpp', + 'nsRssIncomingServer.cpp', + 'nsRssService.cpp', +] + +if CONFIG['MOZ_MOVEMAIL']: + SOURCES += [ + 'nsMovemailIncomingServer.cpp', + 'nsMovemailService.cpp', + ] + +FINAL_LIBRARY = 'mail' + diff --git a/mailnews/local/src/nsLocalMailFolder.cpp b/mailnews/local/src/nsLocalMailFolder.cpp new file mode 100644 index 000000000..14135fe46 --- /dev/null +++ b/mailnews/local/src/nsLocalMailFolder.cpp @@ -0,0 +1,3852 @@ +/* -*- 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 <nsIMsgFolder> srcFolder = do_QueryInterface(m_srcSupport); + if (srcFolder && m_message) + { + nsCString uri; + srcFolder->GetUriForMsg(m_message, uri); + } + } +} + +nsLocalFolderScanState::nsLocalFolderScanState() : m_uidl(nullptr) +{ +} + +nsLocalFolderScanState::~nsLocalFolderScanState() +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// nsMsgLocalMailFolder interface +/////////////////////////////////////////////////////////////////////////////// + +nsMsgLocalMailFolder::nsMsgLocalMailFolder(void) + : mCopyState(nullptr), mHaveReadNameFromDB(false), + mInitialized(false), + mCheckForNewMessagesAfterParsing(false), m_parsingFolder(false), + mDownloadState(DOWNLOAD_STATE_NONE) +{ +} + +nsMsgLocalMailFolder::~nsMsgLocalMailFolder(void) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgLocalMailFolder, + nsMsgDBFolder, + nsICopyMessageListener, + nsIMsgLocalMailFolder) + +//////////////////////////////////////////////////////////////////////////////// + +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<nsIMsgFolderNotificationService> 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<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + if (aListener != this) + mReparseListener = aListener; + // if parsing is synchronous, we need to set m_parsingFolder to + // true before starting. And we need to open the db before + // setting m_parsingFolder to true. +// OpenDatabase(); + rv = msgStore->RebuildIndex(this, mDatabase, aMsgWindow, this); + if (NS_SUCCEEDED(rv)) + m_parsingFolder = true; + + return rv; +} + +// this won't force a reparse of the folder if the db is invalid. +NS_IMETHODIMP +nsMsgLocalMailFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) +{ + return GetDatabaseWOReparse(aMsgDatabase); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetSubFolders(nsISimpleEnumerator **aResult) +{ + if (!mInitialized) + { + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + // need to set this flag here to avoid infinite recursion + mInitialized = true; + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + // This should add all existing folders as sub-folders of this folder. + rv = msgStore->DiscoverSubFolders(this, true); + + nsCOMPtr<nsIFile> path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) + return rv; + + bool directory; + path->IsDirectory(&directory); + if (directory) + { + SetFlag(nsMsgFolderFlags::Mail | nsMsgFolderFlags::Elided | + nsMsgFolderFlags::Directory); + + bool isServer; + GetIsServer(&isServer); + if (isServer) + { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsILocalMailIncomingServer> localMailServer; + localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + // first create the folders on disk (as empty files) + rv = localMailServer->CreateDefaultMailboxes(); + if (NS_FAILED(rv) && rv != NS_MSG_FOLDER_EXISTS) + return rv; + + // must happen after CreateSubFolders, or the folders won't exist. + rv = localMailServer->SetFlagsOnDefaultMailboxes(); + if (NS_FAILED(rv)) + return rv; + } + } + UpdateSummaryTotals(false); + } + + return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER; +} + +nsresult nsMsgLocalMailFolder::GetDatabase() +{ + nsCOMPtr <nsIMsgDatabase> msgDB; + return GetDatabaseWOReparse(getter_AddRefs(msgDB)); +} + +//we treat failure as null db returned +NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWOReparse(nsIMsgDatabase **aDatabase) +{ + NS_ENSURE_ARG(aDatabase); + if (m_parsingFolder) + return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + + nsresult rv = NS_OK; + if (!mDatabase) + { + rv = OpenDatabase(); + if (mDatabase) + { + mDatabase->AddListener(this); + UpdateNewMessages(); + } + } + NS_IF_ADDREF(*aDatabase = mDatabase); + if (mDatabase) + mDatabase->SetLastUseTime(PR_Now()); + return rv; +} + + +// Makes sure the database is open and exists. If the database is out of date, +// then this call will run an async url to reparse the folder. The passed in +// url listener will get called when the url is done. +NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWithReparse(nsIUrlListener *aReparseUrlListener, nsIMsgWindow *aMsgWindow, + nsIMsgDatabase **aMsgDatabase) +{ + nsresult rv = NS_OK; + // if we're already reparsing, just remember the listener so we can notify it + // when we've finished. + if (m_parsingFolder) + { + NS_ASSERTION(!mReparseListener, "can't have an existing listener"); + mReparseListener = aReparseUrlListener; + return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + + if (!mDatabase) + { + nsCOMPtr <nsIFile> pathFile; + rv = GetFilePath(getter_AddRefs(pathFile)); + if (NS_FAILED(rv)) + return rv; + + bool exists; + rv = pathFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv,rv); + if (!exists) + return NS_ERROR_NULL_POINTER; //mDatabase will be null at this point. + + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(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 <nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr <nsIDBFolderInfo> transferInfo; + if (mDatabase) + { + mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if (dbFolderInfo) + { + dbFolderInfo->SetNumMessages(0); + dbFolderInfo->SetNumUnreadMessages(0); + dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo)); + } + dbFolderInfo = nullptr; + + // A backup message database might have been created earlier, for example + // if the user requested a reindex. We'll use the earlier one if we can, + // otherwise we'll try to backup at this point. + if (NS_FAILED(OpenBackupMsgDatabase())) + { + CloseAndBackupFolderDB(EmptyCString()); + if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) + { + mBackupDatabase->RemoveListener(this); + mBackupDatabase = nullptr; + } + } + else + mDatabase->ForceClosed(); + + mDatabase = nullptr; + } + nsCOMPtr <nsIFile> summaryFile; + rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + // Remove summary file. + summaryFile->Remove(false); + + // if it's out of date then reopen with upgrade. + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + + if (transferInfo && mDatabase) + { + SetDBTransferInfo(transferInfo); + mDatabase->SetSummaryValid(false); + } + } + else if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + { + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + } + + if (mDatabase) + { + if (mAddListener) + mDatabase->AddListener(this); + + // if we have to regenerate the folder, run the parser url. + if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + if (NS_FAILED(rv = ParseFolder(aMsgWindow, aReparseUrlListener))) + { + if (rv == NS_MSG_FOLDER_BUSY) + { + 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<nsIMsgDatabase> 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 <nsIMsgDatabase> msgDB; + nsresult rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); + return NS_SUCCEEDED(rv) ? msgDB->EnumerateMessages(result) : rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetFolderURL(nsACString& aUrl) +{ + nsresult rv; + nsCOMPtr<nsIFile> path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) + return rv; + + rv = NS_GetURLSpecFromFile(path, aUrl); + NS_ENSURE_SUCCESS(rv, rv); + + aUrl.Replace(0, strlen("file:"), "mailbox:"); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CreateStorageIfMissing(nsIUrlListener* aUrlListener) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> msgParent; + GetParent(getter_AddRefs(msgParent)); + + // parent is probably not set because *this* was probably created by rdf + // and not by folder discovery. So, we have to compute the parent. + if (!msgParent) + { + nsAutoCString folderName(mURI); + nsAutoCString uri; + int32_t leafPos = folderName.RFindChar('/'); + nsAutoCString parentName(folderName); + if (leafPos > 0) + { + // If there is a hierarchy, there is a parent. + // Don't strip off slash if it's the first character + parentName.SetLength(leafPos); + // get the corresponding RDF resource + // RDF will create the folder resource if it doesn't already exist + nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFResource> 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<nsIMsgFolder> newFolder; + nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolderNotificationService> 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<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgStore->CreateFolder(this, folderName, aNewFolder); + if (rv == NS_MSG_ERROR_INVALID_FOLDER_NAME) + { + ThrowAlertMsg("folderCreationFailed", msgWindow); + } + else if (rv == NS_MSG_FOLDER_EXISTS) + { + ThrowAlertMsg("folderExists", msgWindow); + } + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgFolder> 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<nsIMutableArray> folderArray; + nsCOMPtr<nsIMsgFolder> rootFolder; + nsCOMPtr<nsIArray> allDescendents; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + nsCOMPtr<nsIMsgPluggableStore> 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<nsIMsgFolder> 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 <nsIMsgFolderCompactor> 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<nsIMsgPluggableStore> 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<nsIMsgFolder> trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + uint32_t flags; + nsCString trashUri; + trashFolder->GetURI(trashUri); + trashFolder->GetFlags(&flags); + int32_t totalMessages = 0; + rv = trashFolder->GetTotalMessages(true, &totalMessages); + if (totalMessages <= 0) + { + nsCOMPtr<nsISimpleEnumerator> 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<nsIMsgFolder> parentFolder; + rv = trashFolder->GetParent(getter_AddRefs(parentFolder)); + if (NS_SUCCEEDED(rv) && parentFolder) + { + nsCOMPtr <nsIDBFolderInfo> transferInfo; + trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + trashFolder->SetParent(nullptr); + parentFolder->PropagateDelete(trashFolder, true, msgWindow); + parentFolder->CreateSubfolder(NS_LITERAL_STRING("Trash"), nullptr); + nsCOMPtr<nsIMsgFolder> newTrashFolder; + rv = GetTrashFolder(getter_AddRefs(newTrashFolder)); + if (NS_SUCCEEDED(rv) && newTrashFolder) + { + nsCOMPtr <nsIMsgLocalMailFolder> 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<nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr<nsIMsgDatabase> db; + newTrashFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); + if (dbFolderInfo) + { + dbFolderInfo->SetNumUnreadMessages(0); + dbFolderInfo->SetNumMessages(0); + } + newTrashFolder->UpdateSummaryTotals(true); + } + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::IsChildOfTrash(bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + uint32_t parentFlags = 0; + *result = false; + bool isServer; + nsresult rv = GetIsServer(&isServer); + if (NS_FAILED(rv) || isServer) + return NS_OK; + + rv= GetFlags(&parentFlags); //this is the parent folder + if (parentFlags & nsMsgFolderFlags::Trash) + { + *result = true; + return rv; + } + + nsCOMPtr<nsIMsgFolder> parentFolder; + nsCOMPtr<nsIMsgFolder> thisFolder; + rv = QueryInterface(NS_GET_IID(nsIMsgFolder), (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::Delete() +{ + nsresult rv; + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgDBService->CachedDBForFolder(this, getter_AddRefs(mDatabase)); + if (mDatabase) + { + mDatabase->ForceClosed(); + mDatabase = nullptr; + } + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIFile> summaryFile; + rv = GetSummaryFile(getter_AddRefs(summaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + //Clean up .sbd folder if it exists. + // Remove summary file. + rv = summaryFile->Remove(false); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not delete msg summary file"); + + rv = msgStore->DeleteFolder(this); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + rv = NS_OK; // virtual folders do not have a msgStore file + 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<nsIMsgFolder> 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<nsIMsgFolder> trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + if (folder) + { + nsCOMPtr<nsIMsgCopyService> 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<nsIDocShell> docShell; + aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) + { + bool confirmDeletion = true; + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion); + if (confirmDeletion) + { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString folderName; + rv = aFolder->GetName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + 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<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog) + { + int32_t buttonPressed = 0; + // Default the dialog to "cancel". + const uint32_t buttonFlags = + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1); + bool dummyValue = false; + rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), confirmationStr.get(), + buttonFlags, deleteFolderButtonLabel.get(), + nullptr, nullptr, nullptr, &dummyValue, + &buttonPressed); + NS_ENSURE_SUCCESS(rv, rv); + *aResult = !buttonPressed; // "ok" is in position 0 + } + } + else + *aResult = true; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Rename(const nsAString& aNewName, nsIMsgWindow *msgWindow) +{ + // Renaming to the same name is easy + if (mName.Equals(aNewName)) + return NS_OK; + + nsCOMPtr<nsIMsgFolder> parentFolder; + nsresult rv = GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) + return NS_ERROR_NULL_POINTER; + + rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsCOMPtr<nsIMsgFolder> newFolder; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgStore->RenameFolder(this, aNewName, getter_AddRefs(newFolder)); + if (NS_FAILED(rv)) + { + if (msgWindow) + (void) ThrowAlertMsg((rv == NS_MSG_FOLDER_EXISTS) ? + "folderExists" : "folderRenameFailed", msgWindow); + return rv; + } + + int32_t count = mSubFolders.Count(); + if (newFolder) + { + // Because we just renamed the db, w/o setting the pretty name in it, + // we need to force the pretty name to be correct. + // SetPrettyName won't write the name to the db if it doesn't think the + // name has changed. This hack forces the pretty name to get set in the db. + // We could set the new pretty name on the db before renaming the .msf file, + // but if the rename failed, it would be out of sync. + newFolder->SetPrettyName(EmptyString()); + newFolder->SetPrettyName(aNewName); + bool changed = false; + MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed); + if (changed) + AlertFilterChanged(msgWindow); + + if (count > 0) + newFolder->RenameSubFolders(msgWindow, this); + + // Discover the subfolders inside this folder (this is recursive) + nsCOMPtr<nsISimpleEnumerator> 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<nsIAtom> folderRenameAtom = MsgGetAtom("RenameCompleted"); + newFolder->NotifyFolderEvent(folderRenameAtom); + + nsCOMPtr<nsIMsgFolderNotificationService> 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<nsISimpleEnumerator> enumerator; + rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item)); + if (!msgFolder) + continue; + + nsString folderName; + rv = msgFolder->GetName(folderName); + nsCOMPtr <nsIMsgFolder> newFolder; + AddSubfolder(folderName, getter_AddRefs(newFolder)); + if (newFolder) + { + newFolder->SetPrettyName(folderName); + bool changed = false; + msgFolder->MatchOrChangeFilterDestination(newFolder, true /*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<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIFile> file; + rv = GetFilePath(getter_AddRefs(file)); + + rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + { + // check if we're a real folder by looking at the parent folder. + nsCOMPtr<nsIMsgFolder> parent; + GetParent(getter_AddRefs(parent)); + if (parent) + { + // This little dance creates an empty .msf file and then checks + // if the db is valid - this works if the folder is empty, which + // we don't have a direct way of checking. + nsCOMPtr<nsIMsgDatabase> db; + rv = msgDBService->CreateNewDB(this, getter_AddRefs(db)); + if (db) + { + UpdateSummaryTotals(true); + db->Close(true); + mDatabase = nullptr; + db = nullptr; + rv = msgDBService->OpenFolderDB(this, false, + getter_AddRefs(mDatabase)); + if (NS_FAILED(rv)) + mDatabase = nullptr; + } + } + } + else if (NS_FAILED(rv)) + mDatabase = nullptr; + + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db) +{ + if (!db || !folderInfo || !mPath || mIsServer) + return NS_ERROR_NULL_POINTER; //ducarroz: should we use NS_ERROR_INVALID_ARG? + + nsresult rv; + if (mDatabase) + rv = NS_OK; + else + { + rv = OpenDatabase(); + + if (mAddListener && mDatabase) + mDatabase->AddListener(this); + } + + NS_IF_ADDREF(*db = mDatabase); + if (NS_SUCCEEDED(rv) && *db) + rv = (*db)->GetDBFolderInfo(folderInfo); + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) +{ + NS_ENSURE_ARG_POINTER(element); + nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element); + NS_ENSURE_SUCCESS(rv, rv); + nsCString utf8Name; + rv = element->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<nsIFile> file; + rv = GetFilePath(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + // Use a temporary variable so that we keep mFolderSize on kSizeUnknown + // if GetFileSize() fails. + int64_t folderSize; + rv = file->GetFileSize(&folderSize); + NS_ENSURE_SUCCESS(rv, rv); + + mFolderSize = folderSize; + } + *aSize = mFolderSize; + return NS_OK; +} + +nsresult +nsMsgLocalMailFolder::GetTrashFolder(nsIMsgFolder** result) +{ + NS_ENSURE_ARG_POINTER(result); + nsresult rv; + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv)) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, result); + if (!*result) + rv = NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::DeleteMessages(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<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgsDeleted(messages); + } + + if (!deleteStorage && !isTrashFolder) + { + nsCOMPtr<nsIMsgFolder> trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return copyService->CopyMessages(this, messages, trashFolder, + true, listener, msgWindow, allowUndo); + } + } + else + { + nsCOMPtr <nsIMsgDatabase> msgDB; + rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); + if (NS_SUCCEEDED(rv)) + { + if (deleteStorage && isMove && GetDeleteFromServerOnMove()) + MarkMsgsOnPop3Server(messages, POP3_DELETE); + + nsCOMPtr<nsISupports> msgSupport; + rv = EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) + { + rv = msgStore->DeleteMessages(messages); + nsCOMPtr<nsIMsgDBHdr> 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<nsITransactionManager> txnMgr; + msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + txnMgr->Clear(); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) +{ + nsMsgMessageFlagType msgFlag = 0; + switch (aDispositionFlag) { + case nsIMsgFolder::nsMsgDispositionState_Replied: + msgFlag = nsMsgMessageFlags::Replied; + break; + case nsIMsgFolder::nsMsgDispositionState_Forwarded: + msgFlag = nsMsgMessageFlags::Forwarded; + break; + default: + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMutableArray> 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<nsIMsgPluggableStore> 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<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Marked, + aMarkFlagged); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) +{ + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + + 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<nsIMutableArray> messages; + rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr<nsIMsgPluggableStore> 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<nsIMutableArray> messages; + rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr<nsIMsgPluggableStore> 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<nsIFile> path; + + NS_ASSERTION(!mCopyState, "already copying a msg into this folder"); + if (mCopyState) + return NS_ERROR_FAILURE; // already has a copy in progress + + // get mDatabase set, so we can use it to add new hdrs to this db. + // calling GetDatabase will set mDatabase - we use the comptr + // here to avoid doubling the refcnt on mDatabase. We don't care if this + // fails - we just want to give it a chance. It will definitely fail in + // nsLocalMailFolder::EndCopy because we will have written data to the folder + // and changed its size. + nsCOMPtr <nsIMsgDatabase> msgDB; + GetDatabaseWOReparse(getter_AddRefs(msgDB)); + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) + return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + + mCopyState = new nsLocalMailCopyState(); + NS_ENSURE_TRUE(mCopyState, NS_ERROR_OUT_OF_MEMORY); + + mCopyState->m_dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1); + NS_ENSURE_TRUE(mCopyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCopyState->m_dataBufferSize = COPY_BUFFER_SIZE; + mCopyState->m_destDB = msgDB; + + //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<nsIMsgLocalMailFolder*>(this), &haveSemaphore); + if (NS_SUCCEEDED(rv) && haveSemaphore) + ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + + if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() && + mCopyState->m_newHdr) + { + nsCOMPtr<nsIMutableArray> 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<nsIMsgCopyService> 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<nsISupports> srcSupport = do_QueryInterface(srcFolder); + bool isServer; + nsresult rv = GetIsServer(&isServer); + if (NS_SUCCEEDED(rv) && isServer) + { + NS_ERROR("Destination is the root folder. Cannot move/copy here"); + if (isMove) + srcFolder->NotifyFolderEvent(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<nsIMsgDBHdr> 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<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsITransaction> undoTxn; + nsCOMPtr<nsIArray> 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<nsITransactionManager> 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 <nsIMsgLocalMailFolder> localDstFolder; + QueryInterface(NS_GET_IID(nsIMsgLocalMailFolder), getter_AddRefs(localDstFolder)); + if (localDstFolder) + { + // If we are the trash and a local msg is being moved to us, mark the source + // for delete from server, if so configured. + if (mFlags & nsMsgFolderFlags::Trash) + { + // If we're deleting on all moves, we'll mark this message for deletion when + // we call DeleteMessages on the source folder. So don't mark it for deletion + // here, in that case. + if (!GetDeleteFromServerOnMove()) + localDstFolder->MarkMsgsOnPop3Server(dstHdrs, POP3_DELETE); + } + } + } + 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<nsMsgKey> keyArray(numMsgs); + if (numMsgs > 1) + { + for (uint32_t i = 0; i < numMsgs; i++) + { + nsCOMPtr<nsIMsgDBHdr> aMessage = do_QueryElementAt(messages, i, &rv); + if (NS_SUCCEEDED(rv) && aMessage) + { + nsMsgKey key; + aMessage->GetMessageKey(&key); + keyArray.AppendElement(key); + } + } + + keyArray.Sort(); + + nsCOMPtr<nsIMutableArray> 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<nsIMsgDatabase> msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) + parseMsgState->SetMailDB(msgDb); + } + } + + // undo stuff + if (allowUndo) //no undo for folder move/copy or or move/copy from search window + { + RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn; + if (msgTxn && NS_SUCCEEDED(msgTxn->Init(srcFolder, this, isMove))) + { + msgTxn->SetMsgWindow(msgWindow); + if (isMove) + { + if (mFlags & nsMsgFolderFlags::Trash) + msgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); + else + msgTxn->SetTransactionType(nsIMessenger::eMoveMsg); + } + else + msgTxn->SetTransactionType(nsIMessenger::eCopyMsg); + msgTxn.swap(mCopyState->m_undoMsgTxn); + } + } + + if (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<nsISupports> 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<nsIMsgFolder> newMsgFolder; + nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> messages; + rv = srcFolder->GetMessages(getter_AddRefs(messages)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + bool hasMoreElements = false; + nsCOMPtr<nsISupports> 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 <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(newMsgFolder); + if (localFolder) + { + // normally these would get called from ::EndCopy when the last message + // was finished copying. But since there are no messages, we have to call + // them explicitly. + nsCOMPtr<nsISupports> srcSupports = do_QueryInterface(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<nsISimpleEnumerator> enumerator; + nsresult rv = srcFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> 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<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return msgStore->CopyFolder(srcFolder, this, isMoveFolder, msgWindow, + aListener, newFolderName); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFileMessage(nsIFile* aFile, + nsIMsgDBHdr *msgToReplace, + bool isDraftOrTemplate, + uint32_t newMsgFlags, + const nsACString &aNewMsgKeywords, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener) +{ + NS_ENSURE_ARG_POINTER(aFile); + nsresult rv = NS_ERROR_NULL_POINTER; + nsParseMailMessageState* parseMsgState = nullptr; + int64_t fileSize = 0; + + nsCOMPtr<nsISupports> fileSupport(do_QueryInterface(aFile, &rv)); + + aFile->GetFileSize(&fileSize); + if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize)) + return NS_OK; + + nsCOMPtr<nsIMutableArray> 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<nsIMsgDatabase> msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) + parseMsgState->SetMailDB(msgDb); + + nsCOMPtr<nsIInputStream> inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + + // All or none for adding a message file to the store + if (NS_SUCCEEDED(rv) && fileSize > PR_INT32_MAX) + rv = NS_ERROR_ILLEGAL_VALUE; // may need error code for max msg size + + if (NS_SUCCEEDED(rv) && inputStream) + { + char buffer[5]; + uint32_t readCount; + rv = inputStream->Read(buffer, 5, &readCount); + if (NS_SUCCEEDED(rv)) + { + if (strncmp(buffer, "From ", 5)) + mCopyState->m_dummyEnvelopeNeeded = true; + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream, &rv); + if (NS_SUCCEEDED(rv)) + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + } + } + + mCopyState->m_wholeMsgInStream = true; + if (NS_SUCCEEDED(rv)) + rv = BeginCopy(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 <nsIMsgDBHdr> 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<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsILocalMailIncomingServer> localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + // XXX todo, move all this into nsILocalMailIncomingServer's GetNewMail + // so that we don't have to have RSS foo here. + nsCOMPtr<nsIRssIncomingServer> rssServer = do_QueryInterface(server, &rv); + if (NS_SUCCEEDED(rv)) + return localMailServer->GetNewMail(aWindow, aListener, this, nullptr); + + nsCOMPtr<nsIMsgFolder> inbox; + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inbox)); + } + nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv); + if (NS_SUCCEEDED(rv)) + { + bool valid = false; + nsCOMPtr <nsIMsgDatabase> db; + // this will kick off a reparse if the db is out of date. + rv = localInbox->GetDatabaseWithReparse(nullptr, aWindow, getter_AddRefs(db)); + if (NS_SUCCEEDED(rv)) + { + db->GetSummaryValid(&valid); + rv = valid ? localMailServer->GetNewMail(aWindow, aListener, inbox, nullptr) : + localInbox->SetCheckForNewMessagesAfterParsing(true); + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::WriteStartOfNewMessage() +{ + nsCOMPtr <nsISeekableStream> 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 <nsIMsgDBHdr> 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() +{ + nsCOMPtr<nsIMsgPluggableStore> msgStore; + 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 <nsISeekableStream> 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<nsIMsgLocalMailFolder*>(this), &haveSemaphore); + if (NS_FAILED(rv)) + return rv; + if (!haveSemaphore) + return NS_MSG_FOLDER_BUSY; + + if (!mCopyState) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t readCount; + //allocate one extra byte for '\0' at the end and another extra byte at the + //front to insert a '>' if we have a "From" line + //allocate 2 more for crlf that may be needed for those without crlf at end of file + if ( aLength + mCopyState->m_leftOver + 4 > mCopyState->m_dataBufferSize ) + { + char *newBuffer = (char *) PR_REALLOC(mCopyState->m_dataBuffer, aLength + mCopyState->m_leftOver + 4); + if (!newBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + mCopyState->m_dataBuffer = newBuffer; + mCopyState->m_dataBufferSize = aLength + mCopyState->m_leftOver + 3; + } + + nsCOMPtr <nsISeekableStream> 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<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString dontPreserve; + + // These preferences exist so that extensions can control which properties + // are preserved in the database when a message is moved or copied. All + // properties are preserved except those listed in these preferences + if (aIsMove) + prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove", + 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<nsIUTF8StringEnumerator> 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<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn; + + NS_ASSERTION(mCopyState->m_leftOver == 0, "whoops, something wrong with previous copy"); + mCopyState->m_leftOver = 0; // reset to 0. + // need to reset this in case we're move/copying multiple msgs. + mCopyState->m_fromLineSeen = false; + + // flush the copied message. We need a close at the end to get the + // file size and time updated correctly. + // + // These filestream closes are handled inconsistently in the code. In some + // cases, this is done in EndMessage, while in others it is done here in + // EndCopy. When we do the close in EndMessage, we'll set + // mCopyState->m_fileStream to null since it is no longer needed, and detect + // here the null stream so we know that we don't have to close it here. + // + // Similarly, m_parseMsgState->GetNewMsgHdr() returns a null hdr if the hdr + // has already been processed by EndMessage so it is not doubly added here. + + nsCOMPtr <nsISeekableStream> seekableStream(do_QueryInterface(mCopyState->m_fileStream)); + if (seekableStream) + { + if (mCopyState->m_dummyEnvelopeNeeded) + { + uint32_t bytesWritten; + seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + mCopyState->m_fileStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(CRLF, MSG_LINEBREAK_LEN); + } + rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream, + mCopyState->m_newHdr); + if (NS_SUCCEEDED(rv) && mCopyState->m_newHdr) + mCopyState->m_newHdr->GetMessageKey(&mCopyState->m_curDstKey); + if (multipleCopiesFinished) + mCopyState->m_fileStream->Close(); + else + mCopyState->m_fileStream->Flush(); + } + //Copy the header to the new database + if (mCopyState->m_message) + { + // CopyMessages() goes here, and CopyFileMessages() with metadata to save; + nsCOMPtr<nsIMsgDBHdr> newHdr; + if (!mCopyState->m_parseMsgState) + { + if (mCopyState->m_destDB) + { + if (mCopyState->m_newHdr) + { + newHdr = mCopyState->m_newHdr; + CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message, + 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<nsIMsgDBHdr> newHdr; + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) + { + nsCOMPtr<nsIMsgDatabase> msgDb; + mCopyState->m_parseMsgState->FinishHeader(); + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) + { + nsresult result = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + // we need to copy newHdr because mCopyState will get cleared + // in OnCopyCompleted, but we need OnCopyCompleted to know about + // the newHdr, via mCopyState. And we send a notification about newHdr + // after OnCopyCompleted. + mCopyState->m_newHdr = newHdr; + if (NS_SUCCEEDED(result) && newHdr) + { + // Copy message metadata. + 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<nsISupports> 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<nsIMsgFolderNotificationService> 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<nsIMsgFolder> 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<nsITransactionManager> 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<nsIMsgFolderNotificationService> 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 <nsIMutableArray> 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<nsIPrefBranch> 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<nsIMsgFolder> 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<nsIMsgFolder> srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgLocalMailFolder> localSrcFolder = do_QueryInterface(srcFolder); + if (localSrcFolder) + { + // if we are the trash and a local msg is being moved to us, mark the source + // for delete from server, if so configured. + if (mFlags & nsMsgFolderFlags::Trash) + { + // if we're deleting on all moves, we'll mark this message for deletion when + // we call DeleteMessages on the source folder. So don't mark it for deletion + // here, in that case. + if (!GetDeleteFromServerOnMove()) + localSrcFolder->MarkMsgsOnPop3Server(mCopyState->m_messages, POP3_DELETE); + } + } + // lets delete these all at once - much faster that way + rv = srcFolder->DeleteMessages(mCopyState->m_messages, mCopyState->m_msgWindow, true, true, nullptr, mCopyState->m_allowUndo); + AutoCompact(mCopyState->m_msgWindow); + + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true, 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<nsITransactionManager> 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<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsresult rv; + + if (localUndoTxn) + { + localUndoTxn->GetMsgWindow(getter_AddRefs(msgWindow)); + localUndoTxn->AddSrcKey(key); + localUndoTxn->AddDstKey(mCopyState->m_curDstKey); + } + + // I think this is always true for online to offline copy + mCopyState->m_dummyEnvelopeNeeded = true; + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + uint32_t bytesWritten; + mCopyState->m_fileStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(CRLF, MSG_LINEBREAK_LEN); + + rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream, + mCopyState->m_newHdr); + mCopyState->m_fileStream->Close(); + mCopyState->m_fileStream = nullptr; // all done with the file stream + + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) + { + nsCOMPtr<nsIMsgDatabase> msgDb; + nsCOMPtr<nsIMsgDBHdr> newHdr; + + mCopyState->m_parseMsgState->FinishHeader(); + + rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && newHdr) + { + nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDatabase> srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (srcDB) + { + nsCOMPtr <nsIMsgDBHdr> srcMsgHdr; + srcDB->GetMsgHdrForKey(key, getter_AddRefs(srcMsgHdr)); + if (srcMsgHdr) + CopyPropertiesToMsgHdr(newHdr, srcMsgHdr, mCopyState->m_isMove); + } + rv = GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (NS_SUCCEEDED(rv) && msgDb) + { + msgDb->AddNewHdrToDB(newHdr, true); + if (localUndoTxn) + { + // ** jt - recording the message size for possible undo use; the + // message size is different for pop3 and imap4 messages + uint32_t msgSize; + newHdr->GetMessageSize(&msgSize); + localUndoTxn->AddDstMsgSize(msgSize); + } + } + else + mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db + } + mCopyState->m_parseMsgState->Clear(); + + if (mCopyState->m_listener) // CopyFileMessage() only + mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey); + } + + if (mCopyState->m_fileStream) + mCopyState->m_fileStream->Flush(); + return NS_OK; +} + + +nsresult nsMsgLocalMailFolder::CopyMessagesTo(nsIArray *messages, nsTArray<nsMsgKey> &keyArray, + nsIMsgWindow *aMsgWindow, nsIMsgFolder *dstFolder, + bool isMove) +{ + if (!mCopyState) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsICopyMessageListener> copyListener(do_QueryInterface(dstFolder, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + nsCOMPtr<nsIMsgFolder> 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<nsIStreamListener> streamListener(do_QueryInterface(copyStreamListener, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_curCopyIndex = 0; + // we need to kick off the first message - subsequent messages + // are kicked off by nsMailboxProtocol when it finishes a message + // before starting the next message. Only do this if the source folder + // is a local folder, however. IMAP will handle calling StartMessage for + // each message that gets downloaded, and news doesn't go through here + // because news only downloads one message at a time, and this routine + // is for multiple message copy. + nsCOMPtr <nsIMsgLocalMailFolder> srcLocalFolder = do_QueryInterface(srcFolder); + if (srcLocalFolder) + StartMessage(); + nsCOMPtr<nsIURI> dummyNull; + rv = mCopyState->m_messageService->CopyMessages(keyArray.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<nsIMsgDBHdr> msgHdr(do_QueryInterface(message, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_message = do_QueryInterface(msgHdr, &rv); + + nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + nsCString uri; + srcFolder->GetUriForMsg(msgHdr, uri); + + nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsICopyMessageListener> 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<nsIStreamListener> streamListener(do_QueryInterface(copyStreamListener, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + nsCOMPtr<nsIURI> 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<nsIPop3IncomingServer> curFolderPop3MailServer; + nsCOMArray<nsIPop3IncomingServer> pop3Servers; // servers with msgs deleted... + + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + nsresult rv = GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(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<nsIMsgDBHdr> msgDBHdr (do_QueryElementAt(aMessages, i, &rv)); + + uint32_t flags = 0; + + if (msgDBHdr) + { + msgDBHdr->GetFlags(&flags); + nsCOMPtr <nsIPop3IncomingServer> msgPop3Server = curFolderPop3MailServer; + bool leaveOnServer = false; + bool deleteMailLeftOnServer = false; + // set up defaults, in case there's no x-mozilla-account header + if (curFolderPop3MailServer) + { + curFolderPop3MailServer->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer); + curFolderPop3MailServer->GetLeaveMessagesOnServer(&leaveOnServer); + } + + rv = GetUidlFromFolder(&folderScanState, msgDBHdr); + if (!NS_SUCCEEDED(rv)) + continue; + + if (folderScanState.m_uidl) + { + nsCOMPtr <nsIMsgAccount> account; + rv = accountManager->GetAccount(folderScanState.m_accountKey, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) + { + account->GetIncomingServer(getter_AddRefs(incomingServer)); + nsCOMPtr<nsIPop3IncomingServer> curMsgPop3MailServer = do_QueryInterface(incomingServer); + if (curMsgPop3MailServer) + { + msgPop3Server = curMsgPop3MailServer; + msgPop3Server->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer); + msgPop3Server->GetLeaveMessagesOnServer(&leaveOnServer); + } + } + } + // ignore this header if not partial and leaveOnServer not set... + // or if we can't find the pop3 server. + if (!msgPop3Server || (! (flags & nsMsgMessageFlags::Partial) && !leaveOnServer)) + continue; + // if marking deleted, ignore header if we're not deleting from + // server when deleting locally. + if (aMark == POP3_DELETE && leaveOnServer && !deleteMailLeftOnServer) + continue; + if (folderScanState.m_uidl) + { + msgPop3Server->AddUidlToMark(folderScanState.m_uidl, mark); + // remember this pop server in list of servers with msgs deleted + if (pop3Servers.IndexOfObject(msgPop3Server) == -1) + pop3Servers.AppendObject(msgPop3Server); + } + } + } + if (folderScanState.m_inputStream) + folderScanState.m_inputStream->Close(); + // need to do this for all pop3 mail servers that had messages deleted. + uint32_t serverCount = pop3Servers.Count(); + for (uint32_t index = 0; index < serverCount; index++) + pop3Servers[index]->MarkMessages(); + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DeleteDownloadMsg(nsIMsgDBHdr *aMsgHdr, 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<nsIMsgDBHdr> msgDBHdr = mDownloadMessages[i]; + char *oldMsgId = nullptr; + msgDBHdr->GetMessageId(&oldMsgId); + + // Delete the first match and remove it from the array + if (!PL_strcmp(newMsgId, oldMsgId)) + { + rv = GetDatabase(); + if (!mDatabase) + return rv; + + UpdateNewMsgHdr(msgDBHdr, aMsgHdr); + +#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<nsIMsgWindowCommands> 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<nsIMsgDBHdr> 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<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsILocalMailIncomingServer> localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + 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<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = url->SetSpec(mURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgIncomingServer> 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<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString aSpec; + rv = aUrl->GetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (strstr(aSpec.get(), "uidl=")) + { + nsCOMPtr<nsIPop3Sink> popsink; + rv = popurl->GetPop3Sink(getter_AddRefs(popsink)); + if (NS_SUCCEEDED(rv)) + { + popsink->SetBaseMessageUri(mBaseMessageURI.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<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + nsAutoCString aSpec; + if (aUrl) { + rv = aUrl->GetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (strstr(aSpec.get(), "uidl=")) + { + nsCOMPtr<nsIPop3URL> 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 <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(messageuri.get(), getter_AddRefs(msgDBHdr)); + if (NS_SUCCEEDED(rv)) + { + GetDatabase(); + if (mDatabase) + mDatabase->DeleteHeader(msgDBHdr, nullptr, true, true); + } + + nsCOMPtr<nsIPop3Sink> pop3sink; + nsCString newMessageUri; + rv = popurl->GetPop3Sink(getter_AddRefs(pop3sink)); + if (NS_SUCCEEDED(rv)) + { + pop3sink->GetMessageUri(getter_Copies(newMessageUri)); + if (msgWindow) + { + nsCOMPtr<nsIMsgWindowCommands> 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<nsIUrlListener> saveReparseListener = mReparseListener; + mReparseListener = nullptr; + saveReparseListener->OnStopRunningUrl(aUrl, aExitCode); + } + } + if (mFlags & nsMsgFolderFlags::Inbox) + { + // if we are the inbox and running pop url + nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgIncomingServer> server; + GetServer(getter_AddRefs(server)); + // this is the deferred to account, in the global inbox case + if (server) + server->SetPerformingBiff(false); //biff is over + } + } + return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); +} + +nsresult nsMsgLocalMailFolder::DisplayMoveCopyStatusMsg() +{ + nsresult rv = NS_OK; + if (mCopyState) + { + if (!mCopyState->m_statusFeedback) + { + // get msgWindow from undo txn + nsCOMPtr<nsIMsgWindow> msgWindow; + if (mCopyState->m_undoMsgTxn) + mCopyState->m_undoMsgTxn->GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) + return NS_OK; // not a fatal error. + + msgWindow->GetStatusFeedback(getter_AddRefs(mCopyState->m_statusFeedback)); + } + + if (!mCopyState->m_stringBundle) + { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::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<nsIMsgFolder> msgFolder; + rv = FindSubFolder(escapedFolderName, getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + // we only want to do this if the folder *really* exists, + // so check if it has a parent. Otherwise, we'll create the + // .msf file when we don't want to. + nsCOMPtr <nsIMsgFolder> parent; + msgFolder->GetParent(getter_AddRefs(parent)); + if (!parent) + return NS_ERROR_FAILURE; + + rv = msgFolder->SetFlag(flags); + NS_ENSURE_SUCCESS(rv, rv); + return msgFolder->SetPrettyName(aFolderName); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetCheckForNewMessagesAfterParsing(bool *aCheckForNewMessagesAfterParsing) +{ + NS_ENSURE_ARG_POINTER(aCheckForNewMessagesAfterParsing); + *aCheckForNewMessagesAfterParsing = mCheckForNewMessagesAfterParsing; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::SetCheckForNewMessagesAfterParsing(bool aCheckForNewMessagesAfterParsing) +{ + mCheckForNewMessagesAfterParsing = aCheckForNewMessagesAfterParsing; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::NotifyCompactCompleted() +{ + mExpungedBytes = 0; + m_newMsgs.Clear(); // if compacted, m_newMsgs probably aren't valid. + // if compacted, processing flags probably also aren't valid. + ClearProcessingFlags(); + (void) RefreshSizeOnDisk(); + (void) CloseDBIfFolderNotOpen(); + nsCOMPtr <nsIAtom> 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<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISpamSettings> spamSettings; + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spamFolderURI; + rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI)); + NS_ENSURE_SUCCESS(rv,rv); + + if (aMsgURI) // not end of batch + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgKey msgKey; + rv = msgHdr->GetMessageKey(&msgKey); + NS_ENSURE_SUCCESS(rv, rv); + + // check if this message needs junk classification + uint32_t processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + + if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) + { + nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification, aJunkPercent); + + if (aClassification == nsIJunkMailPlugin::JUNK) + { + bool willMoveMessage = false; + + // don't do the move when we are opening up + // the junk mail folder or the trash folder + // or when manually classifying messages in those folders + if (!(mFlags & nsMsgFolderFlags::Junk || mFlags & nsMsgFolderFlags::Trash)) + { + bool moveOnSpam = false; + rv = spamSettings->GetMoveOnSpam(&moveOnSpam); + NS_ENSURE_SUCCESS(rv,rv); + if (moveOnSpam) + { + nsCOMPtr<nsIMsgFolder> folder; + rv = 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<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); + if (!mSpamKeysToMove.IsEmpty()) + { + nsCOMPtr<nsIMsgFolder> 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<nsIMsgDBHdr> 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<nsIMsgCopyService> 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<uint32_t>(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<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>); + 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<nsIArray> hdrs; + nsresult rv = AddMessageBatch(1, aMessages, getter_AddRefs(hdrs)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDBHdr> 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<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsCOMPtr <nsIOutputStream> outFileStream; + nsCOMPtr<nsIMsgDBHdr> newHdr; + + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) + return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMutableArray> hdrArray = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < aMessageCount; i++) + { + RefPtr<nsParseNewMailState> 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<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgMailSession> 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); + + msgStore->FinishNewMessage(outFileStream, newHdr); + outFileStream->Close(); + outFileStream = nullptr; + newMailParser->OnStopRequest(nullptr, nullptr, NS_OK); + newMailParser->EndMsgDownload(); + hdrArray->AppendElement(newHdr, false); + } + NS_ADDREF(*aHdrArray = hdrArray); + } + ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::WarnIfLocalFileTooBig(nsIMsgWindow *aWindow, + int64_t aSpaceRequested, + bool *aTooBig) +{ + NS_ENSURE_ARG_POINTER(aTooBig); + + *aTooBig = true; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool spaceAvailable = false; + // check if we have a reasonable amount of space left + rv = msgStore->HasSpaceAvailable(this, aSpaceRequested, &spaceAvailable); + if (NS_SUCCEEDED(rv) && spaceAvailable) { + *aTooBig = false; + } else if (rv == NS_ERROR_FILE_TOO_BIG) { + ThrowAlertMsg("mailboxTooLarge", aWindow); + } else { + ThrowAlertMsg("outOfDiskSpace", aWindow); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys, + bool aLocalOnly, nsIUrlListener *aUrlListener, + bool *aAsyncResults) +{ + NS_ENSURE_ARG_POINTER(aKeysToFetch); + NS_ENSURE_ARG_POINTER(aAsyncResults); + + *aAsyncResults = false; + nsCOMPtr <nsIInputStream> inputStream; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < aNumKeys; i++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsCString prevBody; + rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + // ignore messages that already have a preview body. + msgHdr->GetStringProperty("preview", 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<nsIMsgPluggableStore> 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; +} diff --git a/mailnews/local/src/nsLocalMailFolder.h b/mailnews/local/src/nsLocalMailFolder.h new file mode 100644 index 000000000..e8c395342 --- /dev/null +++ b/mailnews/local/src/nsLocalMailFolder.h @@ -0,0 +1,276 @@ +/* -*- 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/. */ + +/** + Interface for representing Local Mail folders. +*/ + +#ifndef nsMsgLocalMailFolder_h__ +#define nsMsgLocalMailFolder_h__ + +#include "mozilla/Attributes.h" +#include "nsMsgDBFolder.h" /* include the interface we are going to support */ +#include "nsAutoPtr.h" +#include "nsICopyMessageListener.h" +#include "nsIFileStreams.h" +#include "nsIPop3IncomingServer.h" // need this for an interface ID +#include "nsMsgTxn.h" +#include "nsIMsgMessageService.h" +#include "nsIMsgParseMailMsgState.h" +#include "nsITransactionManager.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsISeekableStream.h" +#include "nsIMutableArray.h" +#include "nsLocalUndoTxn.h" + +#define COPY_BUFFER_SIZE 16384 + +class nsParseMailMessageState; + +struct nsLocalMailCopyState +{ + nsLocalMailCopyState(); + virtual ~nsLocalMailCopyState(); + + nsCOMPtr <nsIOutputStream> m_fileStream; + nsCOMPtr<nsIMsgPluggableStore> m_msgStore; + nsCOMPtr<nsISupports> m_srcSupport; + /// Source nsIMsgDBHdr instances. + nsCOMPtr<nsIArray> m_messages; + /// Destination nsIMsgDBHdr instances. + nsCOMPtr<nsIMutableArray> m_destMessages; + RefPtr<nsLocalMoveCopyMsgTxn> m_undoMsgTxn; + nsCOMPtr<nsIMsgDBHdr> m_message; // current copy message + nsMsgMessageFlagType m_flags; // current copy message flags + RefPtr<nsParseMailMessageState> m_parseMsgState; + nsCOMPtr<nsIMsgCopyServiceListener> m_listener; + nsCOMPtr<nsIMsgWindow> m_msgWindow; + nsCOMPtr<nsIMsgDatabase> m_destDB; + + // for displaying status; + nsCOMPtr <nsIMsgStatusFeedback> m_statusFeedback; + nsCOMPtr <nsIStringBundle> m_stringBundle; + int64_t m_lastProgressTime; + + nsMsgKey m_curDstKey; + uint32_t m_curCopyIndex; + nsCOMPtr <nsIMsgMessageService> m_messageService; + /// The number of messages in m_messages. + uint32_t m_totalMsgCount; + char *m_dataBuffer; + uint32_t m_dataBufferSize; + uint32_t m_leftOver; + bool m_isMove; + bool m_isFolder; // isFolder move/copy + bool m_dummyEnvelopeNeeded; + bool m_copyingMultipleMessages; + bool m_fromLineSeen; + bool m_allowUndo; + bool m_writeFailed; + bool m_notifyFolderLoaded; + bool m_wholeMsgInStream; + nsCString m_newMsgKeywords; + nsCOMPtr <nsIMsgDBHdr> m_newHdr; +}; + +struct nsLocalFolderScanState +{ + nsLocalFolderScanState(); + ~nsLocalFolderScanState(); + + nsCOMPtr<nsIInputStream> m_inputStream; + nsCOMPtr<nsISeekableStream> m_seekableStream; + nsCOMPtr<nsIMsgPluggableStore> m_msgStore; + nsCString m_header; + nsCString m_accountKey; + const char *m_uidl; // memory is owned by m_header + // false if we need a new input stream for each message + bool m_streamReusable; +}; + +class nsMsgLocalMailFolder : public nsMsgDBFolder, + public nsIMsgLocalMailFolder, + public nsICopyMessageListener +{ +public: + nsMsgLocalMailFolder(void); + NS_DECL_NSICOPYMESSAGELISTENER + NS_DECL_NSIMSGLOCALMAILFOLDER + NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER + NS_DECL_ISUPPORTS_INHERITED + // nsIRDFResource methods: + NS_IMETHOD Init(const char *aURI) override; + + // nsIUrlListener methods + NS_IMETHOD OnStartRunningUrl(nsIURI * aUrl) override; + NS_IMETHOD OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) override; + + // nsIMsgFolder methods: + NS_IMETHOD GetSubFolders(nsISimpleEnumerator* *aResult) override; + NS_IMETHOD GetMsgDatabase(nsIMsgDatabase **aMsgDatabase) override; + + NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) override; + NS_IMETHOD GetMessages(nsISimpleEnumerator **result) override; + NS_IMETHOD UpdateFolder(nsIMsgWindow *aWindow) override; + + NS_IMETHOD CreateSubfolder(const nsAString& folderName ,nsIMsgWindow *msgWindow) override; + + NS_IMETHOD Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) override; + NS_IMETHOD CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow, bool aCompactOfflineAlso) override; + NS_IMETHOD EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener) override; + NS_IMETHOD Delete () override; + NS_IMETHOD DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) override; + NS_IMETHOD CreateStorageIfMissing(nsIUrlListener* urlListener) override; + NS_IMETHOD Rename (const nsAString& aNewName, nsIMsgWindow *msgWindow) override; + NS_IMETHOD RenameSubFolders (nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder) override; + + NS_IMETHOD GetPrettyName(nsAString& prettyName) override; // Override of the base, for top-level mail folder + NS_IMETHOD SetPrettyName(const nsAString& aName) override; + + NS_IMETHOD GetFolderURL(nsACString& url) override; + + NS_IMETHOD GetManyHeadersToDownload(bool *retval) override; + + NS_IMETHOD GetDeletable (bool *deletable) override; + NS_IMETHOD GetSizeOnDisk(int64_t *size) override; + + NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db) override; + + NS_IMETHOD DeleteMessages(nsIArray *messages, + nsIMsgWindow *msgWindow, bool + deleteStorage, bool isMove, + nsIMsgCopyServiceListener* listener, bool allowUndo) override; + NS_IMETHOD CopyMessages(nsIMsgFolder *srcFolder, nsIArray* messages, + bool isMove, nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener, bool isFolder, bool allowUndo) override; + NS_IMETHOD CopyFolder(nsIMsgFolder *srcFolder, bool isMoveFolder, nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener) override; + NS_IMETHOD CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace, + bool isDraftOrTemplate, + uint32_t newMsgFlags, + const nsACString &aNewMsgKeywords, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener) override; + + NS_IMETHOD AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) override; + NS_IMETHOD MarkMessagesRead(nsIArray *aMessages, bool aMarkRead) override; + NS_IMETHOD MarkMessagesFlagged(nsIArray *aMessages, bool aMarkFlagged) override; + NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) override; + NS_IMETHOD MarkThreadRead(nsIMsgThread *thread) override; + NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener) override; + NS_IMETHOD NotifyCompactCompleted() override; + NS_IMETHOD Shutdown(bool shutdownChildren) override; + + NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) override; + NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) override; + + NS_IMETHOD GetName(nsAString& aName) override; + + // Used when headers_only is TRUE + NS_IMETHOD DownloadMessagesForOffline(nsIArray *aMessages, nsIMsgWindow *aWindow) override; + NS_IMETHOD FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys, + bool aLocalOnly, nsIUrlListener *aUrlListener, + bool *aAsyncResults) override; + NS_IMETHOD AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) override; + NS_IMETHOD RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) override; + NS_IMETHOD GetIncomingServerType(nsACString& serverType) override; + +protected: + virtual ~nsMsgLocalMailFolder(); + nsresult CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) override; + nsresult CopyFolderAcrossServer(nsIMsgFolder *srcFolder, nsIMsgWindow *msgWindow,nsIMsgCopyServiceListener* listener); + + nsresult CreateSubFolders(nsIFile *path); + nsresult GetTrashFolder(nsIMsgFolder** trashFolder); + nsresult WriteStartOfNewMessage(); + + // CreateSubfolder, but without the nsIMsgFolderListener notification + nsresult CreateSubfolderInternal(const nsAString& folderName, nsIMsgWindow *msgWindow, + nsIMsgFolder **aNewFolder); + + nsresult IsChildOfTrash(bool *result); + nsresult RecursiveSetDeleteIsMoveTrash(bool bVal); + nsresult ConfirmFolderDeletion(nsIMsgWindow *aMsgWindow, nsIMsgFolder *aFolder, bool *aResult); + + nsresult DeleteMessage(nsISupports *message, nsIMsgWindow *msgWindow, + bool deleteStorage, bool commit); + nsresult GetDatabase() override; + // this will set mDatabase, if successful. It will also create a .msf file + // for an empty local mail folder. It will leave invalid DBs in place, and + // return an error. + nsresult OpenDatabase(); + + // copy message helper + nsresult DisplayMoveCopyStatusMsg(); + + nsresult CopyMessageTo(nsISupports *message, nsIMsgFolder *dstFolder, + nsIMsgWindow *msgWindow, bool isMove); + + /** + * Checks if there's room in the target folder to copy message(s) into. + * If not, handles alerting the user, and sending the copy notifications. + */ + bool CheckIfSpaceForCopy(nsIMsgWindow *msgWindow, + nsIMsgFolder *srcFolder, + nsISupports *srcSupports, + bool isMove, + int64_t totalMsgSize); + + // copy multiple messages at a time from this folder + nsresult CopyMessagesTo(nsIArray *messages, nsTArray<nsMsgKey> &keyArray, + nsIMsgWindow *aMsgWindow, + nsIMsgFolder *dstFolder, + bool isMove); + nsresult InitCopyState(nsISupports* aSupport, nsIArray* messages, + bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow *msgWindow, bool isMoveFolder, bool allowUndo); + nsresult InitCopyMsgHdrAndFileStream(); + // preserve message metadata when moving or copying messages + void CopyPropertiesToMsgHdr(nsIMsgDBHdr *destHdr, nsIMsgDBHdr *srcHdr, bool isMove); + virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override; + nsresult ChangeKeywordForMessages(nsIArray *aMessages, const nsACString& aKeyword, bool add); + bool GetDeleteFromServerOnMove(); + void CopyHdrPropertiesWithSkipList(nsIMsgDBHdr *destHdr, + nsIMsgDBHdr *srcHdr, + const nsCString &skipList); + +protected: + nsLocalMailCopyState *mCopyState; //We only allow one of these at a time + nsCString mType; + bool mHaveReadNameFromDB; + bool mInitialized; + bool mCheckForNewMessagesAfterParsing; + bool m_parsingFolder; + nsCOMPtr<nsIUrlListener> mReparseListener; + nsTArray<nsMsgKey> mSpamKeysToMove; + nsresult setSubfolderFlag(const nsAString& aFolderName, uint32_t flags); + + // state variables for DownloadMessagesForOffline + + // Do we notify the owning window of Delete's before or after + // Adding the new msg? +#define DOWNLOAD_NOTIFY_FIRST 1 +#define DOWNLOAD_NOTIFY_LAST 2 + +#ifndef DOWNLOAD_NOTIFY_STYLE +#define DOWNLOAD_NOTIFY_STYLE DOWNLOAD_NOTIFY_FIRST +#endif + + nsCOMArray<nsIMsgDBHdr> mDownloadMessages; + nsCOMPtr<nsIMsgWindow> mDownloadWindow; + nsMsgKey mDownloadSelectKey; + uint32_t mDownloadState; +#define DOWNLOAD_STATE_NONE 0 +#define DOWNLOAD_STATE_INITED 1 +#define DOWNLOAD_STATE_GOTMSG 2 +#define DOWNLOAD_STATE_DIDSEL 3 + +#if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST + nsMsgKey mDownloadOldKey; + nsMsgKey mDownloadOldParent; + uint32_t mDownloadOldFlags; +#endif +}; + +#endif // nsMsgLocalMailFolder_h__ diff --git a/mailnews/local/src/nsLocalUndoTxn.cpp b/mailnews/local/src/nsLocalUndoTxn.cpp new file mode 100644 index 000000000..a53ac992e --- /dev/null +++ b/mailnews/local/src/nsLocalUndoTxn.cpp @@ -0,0 +1,560 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsIMsgHdr.h" +#include "nsLocalUndoTxn.h" +#include "nsImapCore.h" +#include "nsMsgImapCID.h" +#include "nsIImapService.h" +#include "nsIUrlListener.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsThreadUtils.h" +#include "nsIMsgDatabase.h" +#include "nsIMutableArray.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS_INHERITED(nsLocalMoveCopyMsgTxn, nsMsgTxn, nsIFolderListener) + +nsLocalMoveCopyMsgTxn::nsLocalMoveCopyMsgTxn() : m_srcIsImap4(false), + m_canUndelete(false) +{ +} + +nsLocalMoveCopyMsgTxn::~nsLocalMoveCopyMsgTxn() +{ +} + +nsresult +nsLocalMoveCopyMsgTxn::Init(nsIMsgFolder* srcFolder, nsIMsgFolder* dstFolder, + bool isMove) +{ + nsresult rv; + rv = SetSrcFolder(srcFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetDstFolder(dstFolder); + NS_ENSURE_SUCCESS(rv, rv); + m_isMove = isMove; + + mUndoFolderListener = nullptr; + + nsCString protocolType; + rv = srcFolder->GetURI(protocolType); + protocolType.SetLength(protocolType.FindChar(':')); + if (MsgLowerCaseEqualsLiteral(protocolType, "imap")) + m_srcIsImap4 = true; + return nsMsgTxn::Init(); +} +nsresult +nsLocalMoveCopyMsgTxn::GetSrcIsImap(bool *isImap) +{ + *isImap = m_srcIsImap4; + return NS_OK; +} +nsresult +nsLocalMoveCopyMsgTxn::SetSrcFolder(nsIMsgFolder* srcFolder) +{ + nsresult rv = NS_ERROR_NULL_POINTER; + if (srcFolder) + m_srcFolder = do_GetWeakReference(srcFolder, &rv); + return rv; +} + +nsresult +nsLocalMoveCopyMsgTxn::SetDstFolder(nsIMsgFolder* dstFolder) +{ + nsresult rv = NS_ERROR_NULL_POINTER; + if (dstFolder) + m_dstFolder = do_GetWeakReference(dstFolder, &rv); + return rv; +} + +nsresult +nsLocalMoveCopyMsgTxn::AddSrcKey(nsMsgKey aKey) +{ + m_srcKeyArray.AppendElement(aKey); + return NS_OK; +} + +nsresult +nsLocalMoveCopyMsgTxn::AddSrcStatusOffset(uint32_t aStatusOffset) +{ + m_srcStatusOffsetArray.AppendElement(aStatusOffset); + return NS_OK; +} + + +nsresult +nsLocalMoveCopyMsgTxn::AddDstKey(nsMsgKey aKey) +{ + m_dstKeyArray.AppendElement(aKey); + return NS_OK; +} + +nsresult +nsLocalMoveCopyMsgTxn::AddDstMsgSize(uint32_t msgSize) +{ + m_dstSizeArray.AppendElement(msgSize); + return NS_OK; +} + +nsresult +nsLocalMoveCopyMsgTxn::UndoImapDeleteFlag(nsIMsgFolder* folder, + nsTArray<nsMsgKey>& keyArray, + bool deleteFlag) +{ + nsresult rv = NS_ERROR_FAILURE; + if (m_srcIsImap4) + { + nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIUrlListener> urlListener; + nsCString msgIds; + uint32_t i, count = keyArray.Length(); + urlListener = do_QueryInterface(folder, &rv); + for (i=0; i < count; i++) + { + if (!msgIds.IsEmpty()) + msgIds.Append(','); + msgIds.AppendInt((int32_t) keyArray[i]); + } + // This is to make sure that we are in the selected state + // when executing the imap url; we don't want to load the + // folder so use lite select to do the trick + rv = imapService->LiteSelectFolder(folder, + urlListener, nullptr, nullptr); + if (!deleteFlag) + rv =imapService->AddMessageFlags(folder, + urlListener, nullptr, + msgIds, + kImapMsgDeletedFlag, + true); + else + rv = imapService->SubtractMessageFlags(folder, + urlListener, nullptr, + msgIds, + kImapMsgDeletedFlag, + true); + if (NS_SUCCEEDED(rv) && m_msgWindow) + folder->UpdateFolder(m_msgWindow); + rv = NS_OK; // always return NS_OK to indicate that the src is imap + } + else + rv = NS_ERROR_FAILURE; + return rv; +} + +NS_IMETHODIMP +nsLocalMoveCopyMsgTxn::UndoTransaction() +{ + nsresult rv; + nsCOMPtr<nsIMsgDatabase> dstDB; + + nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgLocalMailFolder> dstlocalMailFolder = do_QueryReferent(m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + dstlocalMailFolder->GetDatabaseWOReparse(getter_AddRefs(dstDB)); + + if (!dstDB) + { + // This will listen for the db reparse finishing, and the corresponding + // FolderLoadedNotification. When it gets that, it will then call + // UndoTransactionInternal. + mUndoFolderListener = new nsLocalUndoFolderListener(this, dstFolder); + if (!mUndoFolderListener) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(mUndoFolderListener); + + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mailSession->AddFolderListener(mUndoFolderListener, nsIFolderListener::event); + NS_ENSURE_SUCCESS(rv,rv); + + rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB)); + NS_ENSURE_SUCCESS(rv,rv); + } + else + rv = UndoTransactionInternal(); + return rv; +} + +nsresult +nsLocalMoveCopyMsgTxn::UndoTransactionInternal() +{ + nsresult rv = NS_ERROR_FAILURE; + + if (mUndoFolderListener) + { + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mailSession->RemoveFolderListener(mUndoFolderListener); + NS_ENSURE_SUCCESS(rv,rv); + + NS_RELEASE(mUndoFolderListener); + mUndoFolderListener = nullptr; + } + + nsCOMPtr<nsIMsgDatabase> srcDB; + nsCOMPtr<nsIMsgDatabase> dstDB; + nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if(NS_FAILED(rv)) return rv; + + rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB)); + if (NS_FAILED(rv)) return rv; + + uint32_t count = m_srcKeyArray.Length(); + uint32_t i; + nsCOMPtr<nsIMsgDBHdr> oldHdr; + nsCOMPtr<nsIMsgDBHdr> newHdr; + + // protect against a bogus undo txn without any source keys + // see bug #179856 for details + NS_ASSERTION(count, "no source keys"); + if (!count) + return NS_ERROR_UNEXPECTED; + + if (m_isMove) + { + if (m_srcIsImap4) + { + bool deleteFlag = true; //message has been deleted -we are trying to undo it + CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deleteFlag); //there could have been a toggle. + rv = UndoImapDeleteFlag(srcFolder, m_srcKeyArray, deleteFlag); + } + else if (m_canUndelete) + { + nsCOMPtr<nsIMutableArray> srcMessages = + do_CreateInstance(NS_ARRAY_CONTRACTID); + nsCOMPtr<nsIMutableArray> dstMessages = + do_CreateInstance(NS_ARRAY_CONTRACTID); + + srcDB->StartBatch(); + for (i = 0; i < count; i++) + { + rv = dstDB->GetMsgHdrForKey(m_dstKeyArray[i], + getter_AddRefs(oldHdr)); + NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header\n"); + if (NS_SUCCEEDED(rv) && oldHdr) + { + rv = srcDB->CopyHdrFromExistingHdr(m_srcKeyArray[i], + oldHdr, true, + getter_AddRefs(newHdr)); + NS_ASSERTION(newHdr, + "fatal ... cannot create new msg header\n"); + if (NS_SUCCEEDED(rv) && newHdr) + { + newHdr->SetStatusOffset(m_srcStatusOffsetArray[i]); + srcDB->UndoDelete(newHdr); + srcMessages->AppendElement(newHdr, false); + // (we want to keep these two lists in sync) + dstMessages->AppendElement(oldHdr, false); + } + } + } + srcDB->EndBatch(); + + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + { + // Remember that we're actually moving things back from the destination + // to the source! + notifier->NotifyMsgsMoveCopyCompleted(true, dstMessages, + srcFolder, srcMessages); + } + + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder); + if (localFolder) + localFolder->MarkMsgsOnPop3Server(srcMessages, POP3_NONE /*deleteMsgs*/); + } + else // undoing a move means moving the messages back. + { + nsCOMPtr<nsIMutableArray> dstMessages = + do_CreateInstance(NS_ARRAY_CONTRACTID); + m_numHdrsCopied = 0; + m_srcKeyArray.Clear(); + for (i = 0; i < count; i++) + { + // GetMsgHdrForKey is not a test for whether the key exists, so check. + bool hasKey = false; + dstDB->ContainsKey(m_dstKeyArray[i], &hasKey); + nsCOMPtr<nsIMsgDBHdr> dstHdr; + if (hasKey) + dstDB->GetMsgHdrForKey(m_dstKeyArray[i], getter_AddRefs(dstHdr)); + if (dstHdr) + { + nsCString messageId; + dstHdr->GetMessageId(getter_Copies(messageId)); + dstMessages->AppendElement(dstHdr, false); + m_copiedMsgIds.AppendElement(messageId); + } + else + { + NS_WARNING("Cannot get old msg header"); + } + } + if (m_copiedMsgIds.Length()) + { + srcFolder->AddFolderListener(this); + m_undoing = true; + return srcFolder->CopyMessages(dstFolder, dstMessages, + true, nullptr, nullptr, false, + false); + } + else + { + // Nothing to do, probably because original messages were deleted. + NS_WARNING("Undo did not find any messages to move"); + } + } + srcDB->SetSummaryValid(true); + } + + dstDB->DeleteMessages(m_dstKeyArray.Length(), m_dstKeyArray.Elements(), nullptr); + dstDB->SetSummaryValid(true); + + return rv; +} + +NS_IMETHODIMP +nsLocalMoveCopyMsgTxn::RedoTransaction() +{ + nsresult rv; + nsCOMPtr<nsIMsgDatabase> srcDB; + nsCOMPtr<nsIMsgDatabase> dstDB; + + nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if(NS_FAILED(rv)) return rv; + rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB)); + if (NS_FAILED(rv)) return rv; + + uint32_t count = m_srcKeyArray.Length(); + uint32_t i; + nsCOMPtr<nsIMsgDBHdr> oldHdr; + nsCOMPtr<nsIMsgDBHdr> newHdr; + + nsCOMPtr<nsIMutableArray> srcMessages = do_CreateInstance(NS_ARRAY_CONTRACTID); + nsCOMPtr <nsISupports> msgSupports; + + for (i=0; i<count; i++) + { + rv = srcDB->GetMsgHdrForKey(m_srcKeyArray[i], + getter_AddRefs(oldHdr)); + NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header\n"); + + if (NS_SUCCEEDED(rv) && oldHdr) + { + msgSupports =do_QueryInterface(oldHdr); + srcMessages->AppendElement(msgSupports, false); + + if (m_canUndelete) + { + rv = dstDB->CopyHdrFromExistingHdr(m_dstKeyArray[i], + oldHdr, true, + getter_AddRefs(newHdr)); + NS_ASSERTION(newHdr, "fatal ... cannot get new msg header\n"); + if (NS_SUCCEEDED(rv) && newHdr) + { + if (i < m_dstSizeArray.Length()) + rv = newHdr->SetMessageSize(m_dstSizeArray[i]); + dstDB->UndoDelete(newHdr); + } + } + } + } + dstDB->SetSummaryValid(true); + + if (m_isMove) + { + if (m_srcIsImap4) + { + // protect against a bogus undo txn without any source keys + // see bug #179856 for details + NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys"); + if (m_srcKeyArray.IsEmpty()) + return NS_ERROR_UNEXPECTED; + + bool deleteFlag = false; //message is un-deleted- we are trying to redo + CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deleteFlag); // there could have been a toggle + rv = UndoImapDeleteFlag(srcFolder, m_srcKeyArray, deleteFlag); + } + else if (m_canUndelete) + { + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder); + if (localFolder) + localFolder->MarkMsgsOnPop3Server(srcMessages, POP3_DELETE /*deleteMsgs*/); + + rv = srcDB->DeleteMessages(m_srcKeyArray.Length(), m_srcKeyArray.Elements(), nullptr); + srcDB->SetSummaryValid(true); + } + else + { + nsCOMPtr<nsIMsgDBHdr> srcHdr; + m_numHdrsCopied = 0; + m_dstKeyArray.Clear(); + for (i = 0; i < count; i++) + { + srcDB->GetMsgHdrForKey(m_srcKeyArray[i], getter_AddRefs(srcHdr)); + NS_ASSERTION(srcHdr, "fatal ... cannot get old msg header\n"); + if (srcHdr) + { + nsCString messageId; + srcHdr->GetMessageId(getter_Copies(messageId)); + m_copiedMsgIds.AppendElement(messageId); + } + } + dstFolder->AddFolderListener(this); + m_undoing = false; + return dstFolder->CopyMessages(srcFolder, srcMessages, true, nullptr, + nullptr, false, false); + } + } + + return rv; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(item)); + if (msgHdr) + { + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_undoing ? m_srcFolder : + m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv,rv); + nsCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + if (m_copiedMsgIds.Contains(messageId)) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + if (m_undoing) + m_srcKeyArray.AppendElement(msgKey); + else + m_dstKeyArray.AppendElement(msgKey); + if (++m_numHdrsCopied == m_copiedMsgIds.Length()) + { + folder->RemoveFolderListener(this); + m_copiedMsgIds.Clear(); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnItemEvent(nsIMsgFolder *aItem, nsIAtom *aEvent) +{ + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsLocalUndoFolderListener, nsIFolderListener) + +nsLocalUndoFolderListener::nsLocalUndoFolderListener(nsLocalMoveCopyMsgTxn *aTxn, nsIMsgFolder *aFolder) +{ + mTxn = aTxn; + mFolder = aFolder; +} + +nsLocalUndoFolderListener::~nsLocalUndoFolderListener() +{ +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP nsLocalUndoFolderListener::OnItemEvent(nsIMsgFolder *aItem, nsIAtom *aEvent) +{ + if (mTxn && mFolder && aItem == mFolder) { + bool isEqual = false; + aEvent->ScriptableEquals(NS_LITERAL_STRING("FolderLoaded"), &isEqual); + if (isEqual) + return mTxn->UndoTransactionInternal(); + } + + return NS_ERROR_FAILURE; +} diff --git a/mailnews/local/src/nsLocalUndoTxn.h b/mailnews/local/src/nsLocalUndoTxn.h new file mode 100644 index 000000000..62f93f20a --- /dev/null +++ b/mailnews/local/src/nsLocalUndoTxn.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsLocalUndoTxn_h__ +#define nsLocalUndoTxn_h__ + +#include "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsIMsgFolder.h" +#include "nsMailboxService.h" +#include "nsMsgTxn.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsIUrlListener.h" +#include "nsIWeakReference.h" +#include "nsIWeakReferenceUtils.h" + +class nsLocalUndoFolderListener; + +class nsLocalMoveCopyMsgTxn : public nsIFolderListener, public nsMsgTxn +{ +public: + nsLocalMoveCopyMsgTxn(); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFOLDERLISTENER + + // overloading nsITransaction methods + NS_IMETHOD UndoTransaction(void) override; + NS_IMETHOD RedoTransaction(void) override; + + // helper + nsresult AddSrcKey(nsMsgKey aKey); + nsresult AddSrcStatusOffset(uint32_t statusOffset); + nsresult AddDstKey(nsMsgKey aKey); + nsresult AddDstMsgSize(uint32_t msgSize); + nsresult SetSrcFolder(nsIMsgFolder* srcFolder); + nsresult GetSrcIsImap(bool *isImap); + nsresult SetDstFolder(nsIMsgFolder* dstFolder); + nsresult Init(nsIMsgFolder* srcFolder, + nsIMsgFolder* dstFolder, bool isMove); + nsresult UndoImapDeleteFlag(nsIMsgFolder* aFolder, + nsTArray<nsMsgKey>& aKeyArray, + bool deleteFlag); + nsresult UndoTransactionInternal(); + // If the store using this undo transaction can "undelete" a message, + // it will call this function on the transaction; This makes undo/redo + // easy because message keys don't change after undo/redo. Otherwise, + // we need to adjust the src or dst keys after every undo/redo action + // to note the new keys. + void SetCanUndelete(bool canUndelete) {m_canUndelete = canUndelete;} + +private: + virtual ~nsLocalMoveCopyMsgTxn(); + nsWeakPtr m_srcFolder; + nsTArray<nsMsgKey> m_srcKeyArray; // used when src is local or imap + nsTArray<uint32_t> m_srcStatusOffsetArray; // used when src is local + nsWeakPtr m_dstFolder; + nsTArray<nsMsgKey> m_dstKeyArray; + bool m_isMove; + bool m_srcIsImap4; + bool m_canUndelete; + nsTArray<uint32_t> m_dstSizeArray; + bool m_undoing; // if false, re-doing + uint32_t m_numHdrsCopied; + nsTArray<nsCString> m_copiedMsgIds; + nsLocalUndoFolderListener *mUndoFolderListener; +}; + +class nsLocalUndoFolderListener : public nsIFolderListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFOLDERLISTENER + + nsLocalUndoFolderListener(nsLocalMoveCopyMsgTxn *aTxn, nsIMsgFolder *aFolder); + +private: + virtual ~nsLocalUndoFolderListener(); + nsLocalMoveCopyMsgTxn *mTxn; + nsIMsgFolder *mFolder; +}; + +#endif diff --git a/mailnews/local/src/nsLocalUtils.cpp b/mailnews/local/src/nsLocalUtils.cpp new file mode 100644 index 000000000..14a6a2f21 --- /dev/null +++ b/mailnews/local/src/nsLocalUtils.cpp @@ -0,0 +1,244 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsLocalUtils.h" +#include "nsIServiceManager.h" +#include "prsystem.h" +#include "nsCOMPtr.h" +#include "prmem.h" +// stuff for temporary root folder hack +#include "nsIMsgAccountManager.h" +#include "nsIMsgIncomingServer.h" +#include "nsIPop3IncomingServer.h" +#include "nsINoIncomingServer.h" +#include "nsMsgBaseCID.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsMsgUtils.h" +#include "nsNetCID.h" + +// it would be really cool to: +// - cache the last hostname->path match +// - if no such server exists, behave like an old-style mailbox URL +// (i.e. return the mail.directory preference or something) +static nsresult +nsGetMailboxServer(const char *uriStr, nsIMsgIncomingServer** aResult) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIURL> aUrl = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = aUrl->SetSpec(nsDependentCString(uriStr)); + if (NS_FAILED(rv)) return rv; + + // retrieve the AccountManager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + // find all local mail "no servers" matching the given hostname + nsCOMPtr<nsIMsgIncomingServer> none_server; + aUrl->SetScheme(NS_LITERAL_CSTRING("none")); + // No unescaping of username or hostname done here. + // The unescaping is done inside of FindServerByURI + rv = accountManager->FindServerByURI(aUrl, false, + getter_AddRefs(none_server)); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*aResult = none_server); + return rv; + } + + // if that fails, look for the rss hosts matching the given hostname + nsCOMPtr<nsIMsgIncomingServer> rss_server; + aUrl->SetScheme(NS_LITERAL_CSTRING("rss")); + rv = accountManager->FindServerByURI(aUrl, false, + getter_AddRefs(rss_server)); + if (NS_SUCCEEDED(rv)) + { + NS_ADDREF(*aResult = rss_server); + return rv; + } +#ifdef HAVE_MOVEMAIL + // find all movemail "servers" matching the given hostname + nsCOMPtr<nsIMsgIncomingServer> movemail_server; + aUrl->SetScheme(NS_LITERAL_CSTRING("movemail")); + rv = accountManager->FindServerByURI(aUrl, false, + getter_AddRefs(movemail_server)); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*aResult = movemail_server); + return rv; + } +#endif /* HAVE_MOVEMAIL */ + + // if that fails, look for the pop hosts matching the given hostname + nsCOMPtr<nsIMsgIncomingServer> server; + if (NS_FAILED(rv)) + { + aUrl->SetScheme(NS_LITERAL_CSTRING("pop3")); + rv = accountManager->FindServerByURI(aUrl, false, + getter_AddRefs(server)); + + // if we can't find a pop server, maybe it's a local message + // in an imap hierarchy. look for an imap server. + if (NS_FAILED(rv)) + { + aUrl->SetScheme(NS_LITERAL_CSTRING("imap")); + rv = accountManager->FindServerByURI(aUrl, false, + getter_AddRefs(server)); + } + } + if (NS_SUCCEEDED(rv)) + { + NS_ADDREF(*aResult = server); + return rv; + } + +// if you fail after looking at all "pop3", "movemail" and "none" servers, you fail. +return rv; +} + +static nsresult +nsLocalURI2Server(const char* uriStr, + nsIMsgIncomingServer ** aResult) +{ + nsresult rv; + + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = nsGetMailboxServer(uriStr, getter_AddRefs(server)); + + NS_IF_ADDREF(*aResult = server); + + return rv; +} + +// given rootURI and rootURI##folder, return on-disk path of folder +nsresult +nsLocalURI2Path(const char* rootURI, const char* uriStr, + nsCString& pathResult) +{ + nsresult rv; + + // verify that rootURI starts with "mailbox:/" or "mailbox-message:/" + if ((PL_strcmp(rootURI, kMailboxRootURI) != 0) && + (PL_strcmp(rootURI, kMailboxMessageRootURI) != 0)) { + return NS_ERROR_FAILURE; + } + + // verify that uristr starts with rooturi + nsAutoCString uri(uriStr); + if (uri.Find(rootURI) != 0) + return NS_ERROR_FAILURE; + + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = nsLocalURI2Server(uriStr, getter_AddRefs(server)); + + if (NS_FAILED(rv)) + return rv; + + // now ask the server what it's root is + // and begin pathResult with the mailbox root + nsCOMPtr<nsIFile> localPath; + rv = server->GetLocalPath(getter_AddRefs(localPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString localNativePath; + + localPath->GetNativePath(localNativePath); + nsEscapeNativePath(localNativePath); + pathResult = localNativePath.get(); + const char *curPos = uriStr + PL_strlen(rootURI); + if (curPos) { + // advance past hostname + while ((*curPos)=='/') curPos++; + while (*curPos && (*curPos)!='/') curPos++; + + nsAutoCString newPath(""); + + // Unescape folder name + if (curPos) { + nsCString unescapedStr; + MsgUnescapeString(nsDependentCString(curPos), 0, unescapedStr); + NS_MsgCreatePathStringFromFolderURI(unescapedStr.get(), newPath, NS_LITERAL_CSTRING("none")); + } else + NS_MsgCreatePathStringFromFolderURI(curPos, newPath, NS_LITERAL_CSTRING("none")); + + pathResult.Append('/'); + pathResult.Append(newPath); + } + + return NS_OK; +} + +/* parses LocalMessageURI + * mailbox-message://folder1/folder2#123?header=none or + * mailbox-message://folder1/folder2#1234&part=1.2 + * + * puts folder URI in folderURI (mailbox://folder1/folder2) + * message key number in key + */ +nsresult nsParseLocalMessageURI(const char* uri, + nsCString& folderURI, + nsMsgKey *key) +{ + if(!key) + return NS_ERROR_NULL_POINTER; + + nsAutoCString uriStr(uri); + int32_t keySeparator = uriStr.FindChar('#'); + if(keySeparator != -1) + { + int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "?&", keySeparator); + folderURI = StringHead(uriStr, keySeparator); + folderURI.Cut(7, 8); // cut out the -message part of mailbox-message: + + nsAutoCString keyStr; + if (keyEndSeparator != -1) + keyStr = Substring(uriStr, keySeparator + 1, + keyEndSeparator - (keySeparator + 1)); + else + keyStr = StringTail(uriStr, uriStr.Length() - (keySeparator + 1)); + + *key = msgKeyFromInt(ParseUint64Str(keyStr.get())); + return NS_OK; + } + return NS_ERROR_FAILURE; + +} + +nsresult nsBuildLocalMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri) +{ + // need to convert mailbox://hostname/.. to mailbox-message://hostname/.. + uri.Append(baseURI); + uri.Append('#'); + uri.AppendInt(key); + return NS_OK; +} + +nsresult nsCreateLocalBaseMessageURI(const nsACString& baseURI, nsCString &baseMessageURI) +{ + nsAutoCString tailURI(baseURI); + + // chop off mailbox:/ + if (tailURI.Find(kMailboxRootURI) == 0) + tailURI.Cut(0, PL_strlen(kMailboxRootURI)); + + baseMessageURI = kMailboxMessageRootURI; + baseMessageURI += tailURI; + + return NS_OK; +} + +void nsEscapeNativePath(nsCString& nativePath) +{ +#if defined(XP_WIN) + nativePath.Insert('/', 0); + MsgReplaceChar(nativePath, '\\', '/'); +#endif +} diff --git a/mailnews/local/src/nsLocalUtils.h b/mailnews/local/src/nsLocalUtils.h new file mode 100644 index 000000000..4419e6baa --- /dev/null +++ b/mailnews/local/src/nsLocalUtils.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +#ifndef NS_LOCALUTILS_H +#define NS_LOCALUTILS_H + +#include "nsStringGlue.h" +#include "nsIMsgIncomingServer.h" + +static const char kMailboxRootURI[] = "mailbox:/"; +static const char kMailboxMessageRootURI[] = "mailbox-message:/"; + +nsresult +nsLocalURI2Path(const char* rootURI, const char* uriStr, nsCString& pathResult); + +nsresult +nsParseLocalMessageURI(const char* uri, nsCString& folderURI, nsMsgKey *key); + +nsresult +nsBuildLocalMessageURI(const char* baseURI, nsMsgKey key, nsCString& uri); + +nsresult +nsCreateLocalBaseMessageURI(const nsACString& baseURI, nsCString &baseMessageURI); + +void +nsEscapeNativePath(nsCString& nativePath); + +#endif //NS_LOCALUTILS_H diff --git a/mailnews/local/src/nsMailboxProtocol.cpp b/mailnews/local/src/nsMailboxProtocol.cpp new file mode 100644 index 000000000..fad77aa93 --- /dev/null +++ b/mailnews/local/src/nsMailboxProtocol.cpp @@ -0,0 +1,725 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" + +#include "nsMailboxProtocol.h" +#include "nscore.h" +#include "nsIOutputStream.h" +#include "nsIInputStreamPump.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsMsgLineBuffer.h" +#include "nsMsgDBCID.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsICopyMsgStreamListener.h" +#include "nsMsgMessageFlags.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "prerror.h" +#include "prprf.h" +#include "nspr.h" + +PRLogModuleInfo *MAILBOX; +#include "nsIFileStreams.h" +#include "nsIStreamTransportService.h" +#include "nsIStreamConverterService.h" +#include "nsIIOService.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +#include "nsIMsgWindow.h" +#include "nsIMimeHeaders.h" +#include "nsIMsgPluggableStore.h" +#include "nsISeekableStream.h" + +#include "nsIMsgMdnGenerator.h" + +/* the output_buffer_size must be larger than the largest possible line + * 2000 seems good for news + * + * jwz: I increased this to 4k since it must be big enough to hold the + * entire button-bar HTML, and with the new "mailto" format, that can + * contain arbitrarily long header fields like "references". + * + * fortezza: proxy auth is huge, buffer increased to 8k (sigh). + */ +#define OUTPUT_BUFFER_SIZE (4096*2) + +nsMailboxProtocol::nsMailboxProtocol(nsIURI * aURI) + : nsMsgProtocol(aURI) +{ + m_lineStreamBuffer =nullptr; + + // initialize the pr log if it hasn't been initialiezed already + if (!MAILBOX) + MAILBOX = PR_NewLogModule("MAILBOX"); +} + +nsMailboxProtocol::~nsMailboxProtocol() +{ + // free our local state + delete m_lineStreamBuffer; +} + +nsresult nsMailboxProtocol::OpenMultipleMsgTransport(uint64_t offset, int32_t size) +{ + nsresult rv; + + nsCOMPtr<nsIStreamTransportService> serv = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX 64-bit + rv = serv->CreateInputTransport(m_multipleMsgMoveCopyStream, int64_t(offset), + int64_t(size), false, + getter_AddRefs(m_transport)); + + return rv; +} + +nsresult nsMailboxProtocol::Initialize(nsIURI * aURL) +{ + NS_PRECONDITION(aURL, "invalid URL passed into MAILBOX Protocol"); + nsresult rv = NS_OK; + if (aURL) + { + rv = aURL->QueryInterface(NS_GET_IID(nsIMailboxUrl), (void **) getter_AddRefs(m_runningUrl)); + if (NS_SUCCEEDED(rv) && m_runningUrl) + { + nsCOMPtr <nsIMsgWindow> window; + rv = m_runningUrl->GetMailboxAction(&m_mailboxAction); + // clear stopped flag on msg window, because we care. + nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl) + { + mailnewsUrl->GetMsgWindow(getter_AddRefs(window)); + if (window) + window->SetStopped(false); + } + + if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox) + { + // Set the length of the file equal to the max progress + nsCOMPtr<nsIFile> file; + GetFileFromURL(aURL, getter_AddRefs(file)); + if (file) + { + int64_t fileSize = 0; + file->GetFileSize(&fileSize); + mailnewsUrl->SetMaxProgress(fileSize); + } + + rv = OpenFileSocket(aURL, 0, -1 /* read in all the bytes in the file */); + } + else + { + // we need to specify a byte range to read in so we read in JUST the message we want. + rv = SetupMessageExtraction(); + if (NS_FAILED(rv)) return rv; + uint32_t aMsgSize = 0; + rv = m_runningUrl->GetMessageSize(&aMsgSize); + NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up"); + SetContentLength(aMsgSize); + mailnewsUrl->SetMaxProgress(aMsgSize); + + if (RunningMultipleMsgUrl()) + { + // if we're running multiple msg url, we clear the event sink because the multiple + // msg urls will handle setting the progress. + mProgressEventSink = nullptr; + } + + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgFolder> folder; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv) && msgHdr) + { + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) + { + nsCOMPtr<nsIInputStream> stream; + int64_t offset = 0; + bool reusable = false; + + rv = folder->GetMsgInputStream(msgHdr, &reusable, getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(stream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + seekableStream->Tell(&offset); + // create input stream transport + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + m_readCount = aMsgSize; + // Save the stream for reuse, but only for multiple URLs. + if (reusable && RunningMultipleMsgUrl()) + m_multipleMsgMoveCopyStream = stream; + else + reusable = false; + rv = sts->CreateInputTransport(stream, offset, + int64_t(aMsgSize), !reusable, + getter_AddRefs(m_transport)); + + m_socketIsOpen = false; + } + } + if (!folder) // must be a .eml file + rv = OpenFileSocket(aURL, 0, aMsgSize); + } + NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up"); + } + } + } + + m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true); + + m_nextState = MAILBOX_READ_FOLDER; + m_initialState = MAILBOX_READ_FOLDER; + mCurrentProgress = 0; + + // do we really need both? + m_tempMessageFile = m_tempMsgFile; + return rv; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// we suppport the nsIStreamListener interface +//////////////////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsMailboxProtocol::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + // extract the appropriate event sinks from the url and initialize them in our protocol data + // the URL should be queried for a nsINewsURL. If it doesn't support a news URL interface then + // we have an error. + if (m_nextState == MAILBOX_READ_FOLDER && m_mailboxParser) + { + // we need to inform our mailbox parser that it's time to start... + m_mailboxParser->OnStartRequest(request, ctxt); + } + return nsMsgProtocol::OnStartRequest(request, ctxt); +} + +bool nsMailboxProtocol::RunningMultipleMsgUrl() +{ + if (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage || m_mailboxAction == nsIMailboxUrl::ActionMoveMessage) + { + uint32_t numMoveCopyMsgs; + nsresult rv = m_runningUrl->GetNumMoveCopyMsgs(&numMoveCopyMsgs); + if (NS_SUCCEEDED(rv) && numMoveCopyMsgs > 1) + return true; + } + return false; +} + +// stop binding is a "notification" informing us that the stream associated with aURL is going away. +NS_IMETHODIMP nsMailboxProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) +{ + nsresult rv; + if (m_nextState == MAILBOX_READ_FOLDER && m_mailboxParser) + { + // we need to inform our mailbox parser that there is no more incoming data... + m_mailboxParser->OnStopRequest(request, ctxt, aStatus); + } + else if (m_nextState == MAILBOX_READ_MESSAGE) + { + DoneReadingMessage(); + } + // I'm not getting cancel status - maybe the load group still has the status. + bool stopped = false; + if (m_runningUrl) + { + nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl) + { + nsCOMPtr <nsIMsgWindow> window; + mailnewsUrl->GetMsgWindow(getter_AddRefs(window)); + if (window) + window->GetStopped(&stopped); + } + + if (!stopped && NS_SUCCEEDED(aStatus) && (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage || m_mailboxAction == nsIMailboxUrl::ActionMoveMessage)) + { + uint32_t numMoveCopyMsgs; + uint32_t curMoveCopyMsgIndex; + rv = m_runningUrl->GetNumMoveCopyMsgs(&numMoveCopyMsgs); + if (NS_SUCCEEDED(rv) && numMoveCopyMsgs > 0) + { + m_runningUrl->GetCurMoveCopyMsgIndex(&curMoveCopyMsgIndex); + if (++curMoveCopyMsgIndex < numMoveCopyMsgs) + { + if (!mSuppressListenerNotifications && m_channelListener) + { + nsCOMPtr<nsICopyMessageStreamListener> listener = do_QueryInterface(m_channelListener, &rv); + if (listener) + { + listener->EndCopy(ctxt, aStatus); + listener->StartMessage(); // start next message. + } + } + m_runningUrl->SetCurMoveCopyMsgIndex(curMoveCopyMsgIndex); + nsCOMPtr <nsIMsgDBHdr> nextMsg; + rv = m_runningUrl->GetMoveCopyMsgHdrForIndex(curMoveCopyMsgIndex, getter_AddRefs(nextMsg)); + if (NS_SUCCEEDED(rv) && nextMsg) + { + uint32_t msgSize = 0; + nsCOMPtr <nsIMsgFolder> msgFolder; + nextMsg->GetFolder(getter_AddRefs(msgFolder)); + NS_ASSERTION(msgFolder, "couldn't get folder for next msg in multiple msg local copy"); + if (msgFolder) + { + nsCString uri; + msgFolder->GetUriForMsg(nextMsg, uri); + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl); + if (msgUrl) + { + msgUrl->SetOriginalSpec(uri.get()); + msgUrl->SetUri(uri.get()); + + uint64_t msgOffset; + nextMsg->GetMessageOffset(&msgOffset); + nextMsg->GetMessageSize(&msgSize); + // now we have to seek to the right position in the file and + // basically re-initialize the transport with the correct message size. + // then, we have to make sure the url keeps running somehow. + nsCOMPtr<nsISupports> urlSupports = do_QueryInterface(m_runningUrl); + // + // put us in a state where we are always notified of incoming data + // + + m_transport = nullptr; // open new stream transport + m_inputStream = nullptr; + m_outputStream = nullptr; + + if (m_multipleMsgMoveCopyStream) + { + rv = OpenMultipleMsgTransport(msgOffset, msgSize); + } + else + { + nsCOMPtr<nsIInputStream> stream; + bool reusable = false; + rv = msgFolder->GetMsgInputStream(nextMsg, &reusable, + getter_AddRefs(stream)); + NS_ASSERTION(!reusable, "We thought streams were not reusable!"); + + if (NS_SUCCEEDED(rv)) + { + // create input stream transport + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + { + m_readCount = msgSize; + rv = sts->CreateInputTransport(stream, msgOffset, + int64_t(msgSize), true, + getter_AddRefs(m_transport)); + } + } + } + + if (NS_SUCCEEDED(rv)) + { + if (!m_inputStream) + rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(m_inputStream)); + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIInputStreamPump> pump; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), m_inputStream); + if (NS_SUCCEEDED(rv)) { + rv = pump->AsyncRead(this, urlSupports); + if (NS_SUCCEEDED(rv)) + m_request = pump; + } + } + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed"); + if (m_loadGroup) + m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus); + m_socketIsOpen = true; // mark the channel as open + return aStatus; + } + } + } + } + else + { + } + } + } + } + // and we want to mark ourselves for deletion or some how inform our protocol manager that we are + // available for another url if there is one. + + // mscott --> maybe we should set our state to done because we don't run multiple urls in a mailbox + // protocol connection.... + m_nextState = MAILBOX_DONE; + + // the following is for smoke test purposes. QA is looking at this "Mailbox Done" string which + // is printed out to the console and determining if the mail app loaded up correctly...obviously + // this solution is not very good so we should look at something better, but don't remove this + // line before talking to me (mscott) and mailnews QA.... + + MOZ_LOG(MAILBOX, mozilla::LogLevel::Info, ("Mailbox Done\n")); + + // when on stop binding is called, we as the protocol are done...let's close down the connection + // releasing all of our interfaces. It's important to remember that this on stop binding call + // is coming from netlib so they are never going to ping us again with on data available. This means + // we'll never be going through the Process loop... + + if (m_multipleMsgMoveCopyStream) + { + m_multipleMsgMoveCopyStream->Close(); + m_multipleMsgMoveCopyStream = nullptr; + } + nsMsgProtocol::OnStopRequest(request, ctxt, aStatus); + return CloseSocket(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// End of nsIStreamListenerSupport +////////////////////////////////////////////////////////////////////////////////////////////// + +nsresult nsMailboxProtocol::DoneReadingMessage() +{ + nsresult rv = NS_OK; + // and close the article file if it was open.... + + if (m_mailboxAction == nsIMailboxUrl::ActionSaveMessageToDisk && m_msgFileOutputStream) + rv = m_msgFileOutputStream->Close(); + + return rv; +} + +nsresult nsMailboxProtocol::SetupMessageExtraction() +{ + // Determine the number of bytes we are going to need to read out of the + // mailbox url.... + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsresult rv = NS_OK; + + NS_ASSERTION(m_runningUrl, "Not running a url"); + if (m_runningUrl) + { + uint32_t messageSize = 0; + m_runningUrl->GetMessageSize(&messageSize); + if (!messageSize) + { + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl, &rv); + NS_ENSURE_SUCCESS(rv,rv); + rv = msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv) && msgHdr) + { + msgHdr->GetMessageSize(&messageSize); + m_runningUrl->SetMessageSize(messageSize); + msgHdr->GetMessageOffset(&m_msgOffset); + } + else + NS_ASSERTION(false, "couldn't get message header"); + } + } + return rv; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// Begin protocol state machine functions... +////////////////////////////////////////////////////////////////////////////////////////////// + +nsresult nsMailboxProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer) +{ + nsresult rv = NS_OK; + // if we were already initialized with a consumer, use it... + nsCOMPtr<nsIStreamListener> consumer = do_QueryInterface(aConsumer); + if (consumer) + m_channelListener = consumer; + + if (aURL) + { + m_runningUrl = do_QueryInterface(aURL); + if (m_runningUrl) + { + // find out from the url what action we are supposed to perform... + rv = m_runningUrl->GetMailboxAction(&m_mailboxAction); + + bool convertData = false; + + // need to check if we're fetching an rfc822 part in order to + // quote a message. + if (m_mailboxAction == nsIMailboxUrl::ActionFetchMessage) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_runningUrl, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString queryStr; + rv = msgUrl->GetQuery(queryStr); + NS_ENSURE_SUCCESS(rv,rv); + + // check if this is a filter plugin requesting the message. + // in that case, set up a text converter + convertData = (queryStr.Find("header=filter") != -1 || + queryStr.Find("header=attach") != -1); + } + else if (m_mailboxAction == nsIMailboxUrl::ActionFetchPart) + { + // when fetching a part, we need to insert a converter into the listener chain order to + // force just the part out of the message. Our channel listener is the consumer we'll + // pass in to AsyncConvertData. + convertData = true; + consumer = m_channelListener; + } + if (convertData) + { + nsCOMPtr<nsIStreamConverterService> streamConverter = do_GetService("@mozilla.org/streamConverters;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIStreamListener> conversionListener; + nsCOMPtr<nsIChannel> channel; + QueryInterface(NS_GET_IID(nsIChannel), getter_AddRefs(channel)); + + rv = streamConverter->AsyncConvertData("message/rfc822", + "*/*", + consumer, channel, getter_AddRefs(m_channelListener)); + } + + if (NS_SUCCEEDED(rv)) + { + switch (m_mailboxAction) + { + case nsIMailboxUrl::ActionParseMailbox: + // extract the mailbox parser.. + rv = m_runningUrl->GetMailboxParser(getter_AddRefs(m_mailboxParser)); + m_nextState = MAILBOX_READ_FOLDER; + break; + case nsIMailboxUrl::ActionSaveMessageToDisk: + // ohhh, display message already writes a msg to disk (as part of a hack) + // so we can piggy back off of that!! We just need to change m_tempMessageFile + // to be the name of our save message to disk file. Since save message to disk + // urls are run without a docshell to display the msg into, we won't be trying + // to display the message after we write it to disk... + { + nsCOMPtr<nsIMsgMessageUrl> messageUrl = do_QueryInterface(m_runningUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + messageUrl->GetMessageFile(getter_AddRefs(m_tempMessageFile)); + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_msgFileOutputStream), m_tempMessageFile, -1, 00600); + NS_ENSURE_SUCCESS(rv, rv); + + bool addDummyEnvelope = false; + messageUrl->GetAddDummyEnvelope(&addDummyEnvelope); + if (addDummyEnvelope) + SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE); + else + ClearFlag(MAILBOX_MSG_PARSE_FIRST_LINE); + } + } + m_nextState = MAILBOX_READ_MESSAGE; + break; + case nsIMailboxUrl::ActionCopyMessage: + case nsIMailboxUrl::ActionMoveMessage: + case nsIMailboxUrl::ActionFetchMessage: + ClearFlag(MAILBOX_MSG_PARSE_FIRST_LINE); + m_nextState = MAILBOX_READ_MESSAGE; + break; + case nsIMailboxUrl::ActionFetchPart: + m_nextState = MAILBOX_READ_MESSAGE; + break; + default: + break; + } + } + + rv = nsMsgProtocol::LoadUrl(aURL, m_channelListener); + + } // if we received an MAILBOX url... + } // if we received a url! + + return rv; +} + +int32_t nsMailboxProtocol::ReadFolderResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length) +{ + // okay we are doing a folder read in 8K chunks of a mail folder.... + // this is almost too easy....we can just forward the data in this stream on to our + // folder parser object!!! + + nsresult rv = NS_OK; + mCurrentProgress += length; + + if (m_mailboxParser) + { + nsCOMPtr <nsIURI> url = do_QueryInterface(m_runningUrl); + rv = m_mailboxParser->OnDataAvailable(nullptr, url, inputStream, sourceOffset, length); // let the parser deal with it... + } + if (NS_FAILED(rv)) + { + m_nextState = MAILBOX_ERROR_DONE; // drop out of the loop.... + return -1; + } + + // now wait for the next 8K chunk to come in..... + SetFlag(MAILBOX_PAUSE_FOR_READ); + + // leave our state alone so when the next chunk of the mailbox comes in we jump to this state + // and repeat....how does this process end? Well when the file is done being read in, core net lib + // will issue an ::OnStopRequest to us...we'll use that as our sign to drop out of this state and to + // close the protocol instance... + + return 0; +} + +int32_t nsMailboxProtocol::ReadMessageResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length) +{ + char *line = nullptr; + uint32_t status = 0; + nsresult rv = NS_OK; + mCurrentProgress += length; + + // if we are doing a move or a copy, forward the data onto the copy handler... + // if we want to display the message then parse the incoming data... + + if (m_channelListener) + { + // just forward the data we read in to the listener... + rv = m_channelListener->OnDataAvailable(this, m_channelContext, inputStream, sourceOffset, length); + } + else + { + bool pauseForMoreData = false; + bool canonicalLineEnding = false; + nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl); + + if (msgurl) + msgurl->GetCanonicalLineEnding(&canonicalLineEnding); + + while ((line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData)) && + !pauseForMoreData) + { + /* When we're sending this line to a converter (ie, + it's a message/rfc822) use the local line termination + convention, not CRLF. This makes text articles get + saved with the local line terminators. Since SMTP + and NNTP mandate the use of CRLF, it is expected that + the local system will convert that to the local line + terminator as it is read. + */ + // mscott - the firstline hack is aimed at making sure we don't write + // out the dummy header when we are trying to display the message. + // The dummy header is the From line with the date tag on it. + if (m_msgFileOutputStream && TestFlag(MAILBOX_MSG_PARSE_FIRST_LINE)) + { + uint32_t count = 0; + rv = m_msgFileOutputStream->Write(line, PL_strlen(line), &count); + if (NS_FAILED(rv)) + break; + + if (canonicalLineEnding) + rv = m_msgFileOutputStream->Write(CRLF, 2, &count); + else + rv = m_msgFileOutputStream->Write(MSG_LINEBREAK, + MSG_LINEBREAK_LEN, &count); + + if (NS_FAILED(rv)) + break; + } + else + SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE); + PR_Free(line); + } + PR_Free(line); + } + + SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for more data to become available... + if (mProgressEventSink && m_runningUrl) + { + int64_t maxProgress; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl)); + mailnewsUrl->GetMaxProgress(&maxProgress); + mProgressEventSink->OnProgress(this, m_channelContext, + mCurrentProgress, + maxProgress); + } + + if (NS_FAILED(rv)) return -1; + + return 0; +} + + +/* + * returns negative if the transfer is finished or error'd out + * + * returns zero or more if the transfer needs to be continued. + */ +nsresult nsMailboxProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, uint64_t offset, uint32_t length) +{ + nsresult rv = NS_OK; + int32_t status = 0; + ClearFlag(MAILBOX_PAUSE_FOR_READ); /* already paused; reset */ + + while(!TestFlag(MAILBOX_PAUSE_FOR_READ)) + { + + switch(m_nextState) + { + case MAILBOX_READ_MESSAGE: + if (inputStream == nullptr) + SetFlag(MAILBOX_PAUSE_FOR_READ); + else + status = ReadMessageResponse(inputStream, offset, length); + break; + case MAILBOX_READ_FOLDER: + if (inputStream == nullptr) + SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for file socket to read in the next chunk... + else + status = ReadFolderResponse(inputStream, offset, length); + break; + case MAILBOX_DONE: + case MAILBOX_ERROR_DONE: + { + nsCOMPtr <nsIMsgMailNewsUrl> anotherUrl = do_QueryInterface(m_runningUrl); + rv = m_nextState == MAILBOX_DONE ? NS_OK : NS_ERROR_FAILURE; + anotherUrl->SetUrlState(false, rv); + m_nextState = MAILBOX_FREE; + } + break; + + case MAILBOX_FREE: + // MAILBOX is a one time use connection so kill it if we get here... + CloseSocket(); + return rv; /* final end */ + + default: /* should never happen !!! */ + m_nextState = MAILBOX_ERROR_DONE; + break; + } + + /* check for errors during load and call error + * state if found + */ + if(status < 0 && m_nextState != MAILBOX_FREE) + { + m_nextState = MAILBOX_ERROR_DONE; + /* don't exit! loop around again and do the free case */ + ClearFlag(MAILBOX_PAUSE_FOR_READ); + } + } /* while(!MAILBOX_PAUSE_FOR_READ) */ + + return rv; +} + +nsresult nsMailboxProtocol::CloseSocket() +{ + // how do you force a release when closing the connection?? + nsMsgProtocol::CloseSocket(); + m_runningUrl = nullptr; + m_mailboxParser = nullptr; + return NS_OK; +} + +// vim: ts=2 sw=2 diff --git a/mailnews/local/src/nsMailboxProtocol.h b/mailnews/local/src/nsMailboxProtocol.h new file mode 100644 index 000000000..53c65b40f --- /dev/null +++ b/mailnews/local/src/nsMailboxProtocol.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsMailboxProtocol_h___ +#define nsMailboxProtocol_h___ + +#include "mozilla/Attributes.h" +#include "nsMsgProtocol.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIChannel.h" +#include "nsIInputStreamPump.h" +#include "nsIOutputStream.h" +#include "nsIMailboxUrl.h" +// State Flags (Note, I use the word state in terms of storing +// state information about the connection (authentication, have we sent +// commands, etc. I do not intend it to refer to protocol state) + +#define MAILBOX_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */ +#define MAILBOX_MSG_PARSE_FIRST_LINE 0x00000002 /* have we read in the first line of the msg */ + +/* states of the machine + */ +typedef enum _MailboxStatesEnum { + MAILBOX_READ_FOLDER, + MAILBOX_FINISH_OPEN_FOLDER, + MAILBOX_OPEN_MESSAGE, + MAILBOX_OPEN_STREAM, + MAILBOX_READ_MESSAGE, + MAILBOX_COMPRESS_FOLDER, + MAILBOX_FINISH_COMPRESS_FOLDER, + MAILBOX_BACKGROUND, + MAILBOX_NULL, + MAILBOX_NULL2, + MAILBOX_DELIVER_QUEUED, + MAILBOX_FINISH_DELIVER_QUEUED, + MAILBOX_DONE, + MAILBOX_ERROR_DONE, + MAILBOX_FREE, + MAILBOX_COPY_MESSAGES, + MAILBOX_FINISH_COPY_MESSAGES +} MailboxStatesEnum; + +class nsMsgLineStreamBuffer; + +class nsMailboxProtocol : public nsMsgProtocol +{ +public: + // Creating a protocol instance requires the URL which needs to be run AND it requires + // a transport layer. + nsMailboxProtocol(nsIURI * aURL); + virtual ~nsMailboxProtocol(); + + // initialization function given a new url and transport layer + nsresult Initialize(nsIURI * aURL); + + // the consumer of the url might be something like an nsIDocShell.... + virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer) override; + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIStreamListener interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_IMETHOD OnStartRequest(nsIRequest *request, nsISupports *ctxt) override; + NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) override; + +private: + nsCOMPtr<nsIMailboxUrl> m_runningUrl; // the nsIMailboxURL that is currently running + nsMailboxAction m_mailboxAction; // current mailbox action associated with this connnection... + uint64_t m_msgOffset; + // Event sink handles + nsCOMPtr<nsIStreamListener> m_mailboxParser; + + // Local state for the current operation + nsMsgLineStreamBuffer * m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream + + // Generic state information -- What state are we in? What state do we want to go to + // after the next response? What was the last response code? etc. + MailboxStatesEnum m_nextState; + MailboxStatesEnum m_initialState; + + int64_t mCurrentProgress; + + // can we just use the base class m_tempMsgFile? + nsCOMPtr<nsIFile> m_tempMessageFile; + nsCOMPtr<nsIOutputStream> m_msgFileOutputStream; + + // this is used to hold the source mailbox file open when move/copying + // multiple messages. + nsCOMPtr<nsIInputStream> m_multipleMsgMoveCopyStream; + + virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, + uint64_t sourceOffset, uint32_t length) override; + virtual nsresult CloseSocket() override; + + nsresult SetupMessageExtraction(); + nsresult OpenMultipleMsgTransport(uint64_t offset, int32_t size); + bool RunningMultipleMsgUrl(); + + //////////////////////////////////////////////////////////////////////////////////////// + // Protocol Methods --> This protocol is state driven so each protocol method is + // designed to re-act to the current "state". I've attempted to + // group them together based on functionality. + //////////////////////////////////////////////////////////////////////////////////////// + + // When parsing a mailbox folder in chunks, this protocol state reads in the current chunk + // and forwards it to the mailbox parser. + int32_t ReadFolderResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length); + int32_t ReadMessageResponse(nsIInputStream * inputStream, uint64_t sourceOffset, uint32_t length); + nsresult DoneReadingMessage(); + + //////////////////////////////////////////////////////////////////////////////////////// + // End of Protocol Methods + //////////////////////////////////////////////////////////////////////////////////////// +}; + +#endif // nsMailboxProtocol_h___ + + + + + + diff --git a/mailnews/local/src/nsMailboxServer.cpp b/mailnews/local/src/nsMailboxServer.cpp new file mode 100644 index 000000000..005204f0d --- /dev/null +++ b/mailnews/local/src/nsMailboxServer.cpp @@ -0,0 +1,33 @@ +/** + * 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 "nsMailboxServer.h" +#include "nsLocalMailFolder.h" + +NS_IMETHODIMP +nsMailboxServer::GetLocalStoreType(nsACString& type) +{ + type.AssignLiteral("mailbox"); + return NS_OK; +} + +NS_IMETHODIMP +nsMailboxServer::GetLocalDatabaseType(nsACString& type) +{ + type.AssignLiteral("mailbox"); + return NS_OK; +} + +nsresult +nsMailboxServer::CreateRootFolderFromUri(const nsCString &serverUri, + nsIMsgFolder **rootFolder) +{ + nsMsgLocalMailFolder *newRootFolder = new nsMsgLocalMailFolder; + if (!newRootFolder) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*rootFolder = newRootFolder); + newRootFolder->Init(serverUri.get()); + return NS_OK; +} diff --git a/mailnews/local/src/nsMailboxServer.h b/mailnews/local/src/nsMailboxServer.h new file mode 100644 index 000000000..70e914f52 --- /dev/null +++ b/mailnews/local/src/nsMailboxServer.h @@ -0,0 +1,22 @@ +/** + * 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/. */ + +#ifndef nsMailboxServer_h__ +#define nsMailboxServer_h__ + +#include "mozilla/Attributes.h" +#include "nsMsgIncomingServer.h" + +class nsMailboxServer : public nsMsgIncomingServer +{ +public: + NS_IMETHOD GetLocalStoreType(nsACString& type) override; + NS_IMETHOD GetLocalDatabaseType(nsACString& type) override; +protected: + virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri, + nsIMsgFolder **rootFolder) override; +}; + +#endif diff --git a/mailnews/local/src/nsMailboxService.cpp b/mailnews/local/src/nsMailboxService.cpp new file mode 100644 index 000000000..00a0d87c8 --- /dev/null +++ b/mailnews/local/src/nsMailboxService.cpp @@ -0,0 +1,677 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... +#include "nsCOMPtr.h" + +#include "nsMailboxService.h" +#include "nsMailboxUrl.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMailboxProtocol.h" +#include "nsIMsgDatabase.h" +#include "nsMsgDBCID.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" +#include "nsLocalUtils.h" +#include "nsMsgLocalCID.h" +#include "nsMsgBaseCID.h" +#include "nsIDocShell.h" +#include "nsIPop3Service.h" +#include "nsMsgUtils.h" +#include "nsNetUtil.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIWebNavigation.h" +#include "prprf.h" +#include "nsIMsgHdr.h" +#include "nsIFileURL.h" +#include "mozilla/RefPtr.h" + +nsMailboxService::nsMailboxService() +{ + mPrintingOperation = false; +} + +nsMailboxService::~nsMailboxService() +{} + +NS_IMPL_ISUPPORTS(nsMailboxService, nsIMailboxService, nsIMsgMessageService, nsIProtocolHandler, nsIMsgMessageFetchPartService) + +nsresult nsMailboxService::ParseMailbox(nsIMsgWindow *aMsgWindow, nsIFile *aMailboxPath, nsIStreamListener *aMailboxParser, + nsIUrlListener * aUrlListener, nsIURI ** aURL) +{ + NS_ENSURE_ARG_POINTER(aMailboxPath); + + nsresult rv; + nsCOMPtr<nsIMailboxUrl> mailboxurl = + do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mailboxurl) + { + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl); + // okay now generate the url string + nsCString mailboxPath; + + aMailboxPath->GetNativePath(mailboxPath); + nsAutoCString buf; + MsgEscapeURL(mailboxPath, + nsINetUtil::ESCAPE_URL_MINIMAL | nsINetUtil::ESCAPE_URL_FORCED, buf); + nsEscapeNativePath(buf); + url->SetUpdatingFolder(true); + url->SetMsgWindow(aMsgWindow); + nsAutoCString uriSpec("mailbox://"); + uriSpec.Append(buf); + rv = url->SetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + + mailboxurl->SetMailboxParser(aMailboxParser); + if (aUrlListener) + url->RegisterListener(aUrlListener); + + rv = RunMailboxUrl(url, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (aURL) + { + *aURL = url; + NS_IF_ADDREF(*aURL); + } + } + + return rv; +} + +nsresult nsMailboxService::CopyMessage(const char * aSrcMailboxURI, + nsIStreamListener * aMailboxCopyHandler, + bool moveMessage, + nsIUrlListener * aUrlListener, + nsIMsgWindow *aMsgWindow, + nsIURI **aURL) +{ + nsMailboxAction mailboxAction = nsIMailboxUrl::ActionMoveMessage; + if (!moveMessage) + mailboxAction = nsIMailboxUrl::ActionCopyMessage; + return FetchMessage(aSrcMailboxURI, aMailboxCopyHandler, aMsgWindow, aUrlListener, nullptr, mailboxAction, nullptr, aURL); +} + +nsresult nsMailboxService::CopyMessages(uint32_t aNumKeys, + nsMsgKey* aMsgKeys, + nsIMsgFolder *srcFolder, + nsIStreamListener * aMailboxCopyHandler, + bool moveMessage, + nsIUrlListener * aUrlListener, + nsIMsgWindow *aMsgWindow, + nsIURI **aURL) +{ + nsresult rv = NS_OK; + NS_ENSURE_ARG(srcFolder); + NS_ENSURE_ARG(aMsgKeys); + nsCOMPtr<nsIMailboxUrl> mailboxurl; + + nsMailboxAction actionToUse = nsIMailboxUrl::ActionMoveMessage; + if (!moveMessage) + actionToUse = nsIMailboxUrl::ActionCopyMessage; + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgDatabase> db; + srcFolder->GetMsgDatabase(getter_AddRefs(db)); + if (db) + { + db->GetMsgHdrForKey(aMsgKeys[0], getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsCString uri; + srcFolder->GetUriForMsg(msgHdr, uri); + rv = PrepareMessageUrl(uri.get(), aUrlListener, actionToUse , getter_AddRefs(mailboxurl), aMsgWindow); + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIURI> url = do_QueryInterface(mailboxurl); + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(url)); + nsCOMPtr<nsIMailboxUrl> mailboxUrl (do_QueryInterface(url)); + msgUrl->SetMsgWindow(aMsgWindow); + + mailboxUrl->SetMoveCopyMsgKeys(aMsgKeys, aNumKeys); + rv = RunMailboxUrl(url, aMailboxCopyHandler); + } + } + } + if (aURL && mailboxurl) + CallQueryInterface(mailboxurl, aURL); + + return rv; +} + +nsresult nsMailboxService::FetchMessage(const char* aMessageURI, + nsISupports * aDisplayConsumer, + nsIMsgWindow * aMsgWindow, + nsIUrlListener * aUrlListener, + const char * aFileName, /* only used by open attachment... */ + nsMailboxAction mailboxAction, + const char * aCharsetOverride, + nsIURI ** aURL) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMailboxUrl> mailboxurl; + nsMailboxAction actionToUse = mailboxAction; + nsCOMPtr<nsIURI> url; + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl; + nsAutoCString uriString(aMessageURI); + + if (!strncmp(aMessageURI, "file:", 5)) + { + int64_t fileSize; + nsCOMPtr<nsIURI> fileUri; + rv = NS_NewURI(getter_AddRefs(fileUri), aMessageURI); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFile> file; + rv = fileUrl->GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + file->GetFileSize(&fileSize); + uriString.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:")); + uriString.Append(NS_LITERAL_CSTRING("&number=0")); + rv = NS_NewURI(getter_AddRefs(url), uriString); + NS_ENSURE_SUCCESS(rv, rv); + + msgUrl = do_QueryInterface(url); + if (msgUrl) + { + msgUrl->SetMsgWindow(aMsgWindow); + nsCOMPtr <nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgUrl, &rv); + mailboxUrl->SetMessageSize((uint32_t) fileSize); + nsCOMPtr <nsIMsgHeaderSink> headerSink; + // need to tell the header sink to capture some headers to create a fake db header + // so we can do reply to a .eml file or a rfc822 msg attachment. + if (aMsgWindow) + aMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + { + nsCOMPtr <nsIMsgDBHdr> dummyHeader; + headerSink->GetDummyMsgHeader(getter_AddRefs(dummyHeader)); + if (dummyHeader) + dummyHeader->SetMessageSize((uint32_t) fileSize); + } + } + } + else + { + // this happens with forward inline of message/rfc822 attachment + // opened in a stand-alone msg window. + int32_t typeIndex = uriString.Find("&type=application/x-message-display"); + if (typeIndex != -1) + { + uriString.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1); + rv = NS_NewURI(getter_AddRefs(url), uriString.get()); + mailboxurl = do_QueryInterface(url); + } + else + rv = PrepareMessageUrl(aMessageURI, aUrlListener, actionToUse , getter_AddRefs(mailboxurl), aMsgWindow); + + if (NS_SUCCEEDED(rv)) + { + url = do_QueryInterface(mailboxurl); + msgUrl = do_QueryInterface(url); + msgUrl->SetMsgWindow(aMsgWindow); + if (aFileName) + msgUrl->SetFileName(nsDependentCString(aFileName)); + } + } + + nsCOMPtr<nsIMsgI18NUrl> i18nurl(do_QueryInterface(msgUrl)); + if (i18nurl) + i18nurl->SetCharsetOverRide(aCharsetOverride); + + // instead of running the mailbox url like we used to, let's try to run the url in the docshell... + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv)); + // if we were given a docShell, run the url in the docshell..otherwise just run it normally. + if (NS_SUCCEEDED(rv) && docShell) + { + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + // DIRTY LITTLE HACK --> if we are opening an attachment we want the docshell to + // treat this load as if it were a user click event. Then the dispatching stuff will be much + // happier. + if (mailboxAction == nsIMailboxUrl::ActionFetchPart) + { + docShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink); + } + rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false); + } + else + rv = RunMailboxUrl(url, aDisplayConsumer); + + if (aURL && mailboxurl) + CallQueryInterface(mailboxurl, aURL); + + return rv; +} + +NS_IMETHODIMP nsMailboxService::FetchMimePart(nsIURI *aURI, const char *aMessageURI, nsISupports *aDisplayConsumer, nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIURI **aURL) +{ + nsresult rv; + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aURI, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + msgUrl->SetMsgWindow(aMsgWindow); + + // set up the url listener + if (aUrlListener) + msgUrl->RegisterListener(aUrlListener); + + return RunMailboxUrl(msgUrl, aDisplayConsumer); +} + +NS_IMETHODIMP nsMailboxService::DisplayMessage(const char* aMessageURI, + nsISupports * aDisplayConsumer, + nsIMsgWindow * aMsgWindow, + nsIUrlListener * aUrlListener, + const char * aCharsetOveride, + nsIURI ** aURL) +{ + return FetchMessage(aMessageURI, aDisplayConsumer, + aMsgWindow,aUrlListener, nullptr, + nsIMailboxUrl::ActionFetchMessage, aCharsetOveride, aURL); +} + +NS_IMETHODIMP +nsMailboxService::StreamMessage(const char *aMessageURI, + nsISupports *aConsumer, + nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener, + bool /* aConvertData */, + const nsACString &aAdditionalHeader, + bool aLocalOnly, + nsIURI **aURL) +{ + // The mailbox protocol object will look for "header=filter" or + // "header=attach" to decide if it wants to convert the data instead of + // using aConvertData. It turns out to be way too hard to pass aConvertData + // all the way over to the mailbox protocol object. + nsAutoCString aURIString(aMessageURI); + if (!aAdditionalHeader.IsEmpty()) + { + aURIString.FindChar('?') == -1 ? aURIString += "?" : aURIString += "&"; + aURIString += "header="; + aURIString += aAdditionalHeader; + } + + return FetchMessage(aURIString.get(), aConsumer, aMsgWindow, aUrlListener, nullptr, + nsIMailboxUrl::ActionFetchMessage, nullptr, aURL); +} + +NS_IMETHODIMP nsMailboxService::StreamHeaders(const char *aMessageURI, + nsIStreamListener *aConsumer, + nsIUrlListener *aUrlListener, + bool aLocalOnly, + nsIURI **aURL) +{ + NS_ENSURE_ARG_POINTER(aMessageURI); + NS_ENSURE_ARG_POINTER(aConsumer); + nsAutoCString folderURI; + nsMsgKey msgKey; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = DecomposeMailboxURI(aMessageURI, getter_AddRefs(folder), &msgKey); + if (msgKey == nsMsgKey_None) + return NS_MSG_MESSAGE_NOT_FOUND; + + nsCOMPtr<nsIInputStream> inputStream; + int64_t messageOffset; + uint32_t messageSize; + rv = folder->GetOfflineFileStream(msgKey, &messageOffset, &messageSize, getter_AddRefs(inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + // GetOfflineFileStream returns NS_OK but null inputStream when there is an error getting the database + if (!inputStream) + return NS_ERROR_FAILURE; + return MsgStreamMsgHeaders(inputStream, aConsumer); +} + + +NS_IMETHODIMP nsMailboxService::IsMsgInMemCache(nsIURI *aUrl, + nsIMsgFolder *aFolder, + bool *aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMailboxService::OpenAttachment(const char *aContentType, + const char *aFileName, + const char *aUrl, + const char *aMessageUri, + nsISupports *aDisplayConsumer, + nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener) +{ + nsCOMPtr <nsIURI> URL; + nsAutoCString urlString(aUrl); + urlString += "&type="; + urlString += aContentType; + urlString += "&filename="; + urlString += aFileName; + CreateStartupUrl(urlString.get(), getter_AddRefs(URL)); + nsresult rv; + + // try to run the url in the docshell... + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv)); + // if we were given a docShell, run the url in the docshell..otherwise just run it normally. + if (NS_SUCCEEDED(rv) && docShell) + { + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + // DIRTY LITTLE HACK --> since we are opening an attachment we want the docshell to + // treat this load as if it were a user click event. Then the dispatching stuff will be much + // happier. + docShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink); + return docShell->LoadURI(URL, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false); + } + return RunMailboxUrl(URL, aDisplayConsumer); + +} + + +NS_IMETHODIMP +nsMailboxService::SaveMessageToDisk(const char *aMessageURI, + nsIFile *aFile, + bool aAddDummyEnvelope, + nsIUrlListener *aUrlListener, + nsIURI **aURL, + bool canonicalLineEnding, + nsIMsgWindow *aMsgWindow) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMailboxUrl> mailboxurl; + + rv = PrepareMessageUrl(aMessageURI, aUrlListener, nsIMailboxUrl::ActionSaveMessageToDisk, getter_AddRefs(mailboxurl), aMsgWindow); + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(mailboxurl); + if (msgUrl) + { + msgUrl->SetMessageFile(aFile); + msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope); + msgUrl->SetCanonicalLineEnding(canonicalLineEnding); + } + + nsCOMPtr<nsIURI> url = do_QueryInterface(mailboxurl); + rv = RunMailboxUrl(url); + } + + if (aURL && mailboxurl) + CallQueryInterface(mailboxurl, aURL); + + return rv; +} + +NS_IMETHODIMP nsMailboxService::GetUrlForUri(const char *aMessageURI, nsIURI **aURL, nsIMsgWindow *aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(aURL); + if (!strncmp(aMessageURI, "file:", 5) || PL_strstr(aMessageURI, "type=application/x-message-display") + || !strncmp(aMessageURI, "mailbox:", 8)) + return NS_NewURI(aURL, aMessageURI); + + nsresult rv = NS_OK; + nsCOMPtr<nsIMailboxUrl> mailboxurl; + rv = PrepareMessageUrl(aMessageURI, nullptr, nsIMailboxUrl::ActionFetchMessage, getter_AddRefs(mailboxurl), aMsgWindow); + if (NS_SUCCEEDED(rv) && mailboxurl) + rv = CallQueryInterface(mailboxurl, aURL); + return rv; +} + +// Takes a mailbox url, this method creates a protocol instance and loads the url +// into the protocol instance. +nsresult nsMailboxService::RunMailboxUrl(nsIURI * aMailboxUrl, nsISupports * aDisplayConsumer) +{ + // create a protocol instance to run the url.. + nsresult rv = NS_OK; + nsMailboxProtocol * protocol = new nsMailboxProtocol(aMailboxUrl); + + if (protocol) + { + rv = protocol->Initialize(aMailboxUrl); + if (NS_FAILED(rv)) + { + delete protocol; + return rv; + } + NS_ADDREF(protocol); + rv = protocol->LoadUrl(aMailboxUrl, aDisplayConsumer); + NS_RELEASE(protocol); // after loading, someone else will have a ref cnt on the mailbox + } + + return rv; +} + +// This function takes a message uri, converts it into a file path & msgKey +// pair. It then turns that into a mailbox url object. It also registers a url +// listener if appropriate. AND it can take in a mailbox action and set that field +// on the returned url as well. +nsresult nsMailboxService::PrepareMessageUrl(const char * aSrcMsgMailboxURI, nsIUrlListener * aUrlListener, + nsMailboxAction aMailboxAction, nsIMailboxUrl ** aMailboxUrl, + nsIMsgWindow *msgWindow) +{ + nsresult rv = CallCreateInstance(NS_MAILBOXURL_CONTRACTID, aMailboxUrl); + if (NS_SUCCEEDED(rv) && aMailboxUrl && *aMailboxUrl) + { + // okay now generate the url string + char * urlSpec; + nsAutoCString folderURI; + nsMsgKey msgKey; + nsCString folderPath; + const char *part = PL_strstr(aSrcMsgMailboxURI, "part="); + const char *header = PL_strstr(aSrcMsgMailboxURI, "header="); + rv = nsParseLocalMessageURI(aSrcMsgMailboxURI, folderURI, &msgKey); + NS_ENSURE_SUCCESS(rv,rv); + rv = nsLocalURI2Path(kMailboxRootURI, folderURI.get(), folderPath); + + if (NS_SUCCEEDED(rv)) + { + // set up the url spec and initialize the url with it. + nsAutoCString buf; + MsgEscapeURL(folderPath, + nsINetUtil::ESCAPE_URL_DIRECTORY | nsINetUtil::ESCAPE_URL_FORCED, buf); + if (mPrintingOperation) + urlSpec = PR_smprintf("mailbox://%s?number=%lu&header=print", buf.get(), msgKey); + else if (part) + urlSpec = PR_smprintf("mailbox://%s?number=%lu&%s", buf.get(), msgKey, part); + else if (header) + urlSpec = PR_smprintf("mailbox://%s?number=%lu&%s", buf.get(), msgKey, header); + else + urlSpec = PR_smprintf("mailbox://%s?number=%lu", buf.get(), msgKey); + + nsCOMPtr <nsIMsgMailNewsUrl> url = do_QueryInterface(*aMailboxUrl); + rv = url->SetSpec(nsDependentCString(urlSpec)); + NS_ENSURE_SUCCESS(rv, rv); + + PR_smprintf_free(urlSpec); + + (*aMailboxUrl)->SetMailboxAction(aMailboxAction); + + // set up the url listener + if (aUrlListener) + rv = url->RegisterListener(aUrlListener); + + url->SetMsgWindow(msgWindow); + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(url); + if (msgUrl) + { + msgUrl->SetOriginalSpec(aSrcMsgMailboxURI); + msgUrl->SetUri(aSrcMsgMailboxURI); + } + + } // if we got a url + } // if we got a url + + return rv; +} + +NS_IMETHODIMP nsMailboxService::GetScheme(nsACString &aScheme) +{ + aScheme = "mailbox"; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxService::GetDefaultPort(int32_t *aDefaultPort) +{ + NS_ENSURE_ARG_POINTER(aDefaultPort); + *aDefaultPort = -1; // mailbox doesn't use a port!!!!! + return NS_OK; +} + +NS_IMETHODIMP nsMailboxService::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxService::GetProtocolFlags(uint32_t *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = URI_STD | URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT | + URI_DANGEROUS_TO_LOAD | URI_FORBIDS_COOKIE_ACCESS +#ifdef IS_ORIGIN_IS_FULL_SPEC_DEFINED + | ORIGIN_IS_FULL_SPEC +#endif + ; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxService::NewURI(const nsACString &aSpec, + const char *aOriginCharset, + nsIURI *aBaseURI, + nsIURI **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = 0; + nsresult rv; + nsCOMPtr<nsIURI> aMsgUri = do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // SetSpec calls below may fail if the mailbox url is of the form + // mailbox://<account>/<mailbox name>?... instead of + // mailbox://<path to folder>?.... This is the case for pop3 download urls. + // We know this, and the failure is harmless. + if (aBaseURI) + { + nsAutoCString newSpec; + rv = aBaseURI->Resolve(aSpec, newSpec); + NS_ENSURE_SUCCESS(rv, rv); + (void) aMsgUri->SetSpec(newSpec); + } + else + { + (void) aMsgUri->SetSpec(aSpec); + } + aMsgUri.swap(*_retval); + + return rv; +} + +NS_IMETHODIMP nsMailboxService::NewChannel(nsIURI *aURI, nsIChannel **_retval) +{ + return NewChannel2(aURI, nullptr, _retval); +} + +NS_IMETHODIMP nsMailboxService::NewChannel2(nsIURI *aURI, + nsILoadInfo *aLoadInfo, + nsIChannel **_retval) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv = NS_OK; + nsAutoCString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + if (spec.Find("?uidl=") >= 0 || spec.Find("&uidl=") >= 0) + { + nsCOMPtr<nsIProtocolHandler> handler = + do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIURI> pop3Uri; + + rv = handler->NewURI(spec, "" /* ignored */, aURI, getter_AddRefs(pop3Uri)); + NS_ENSURE_SUCCESS(rv, rv); + return handler->NewChannel2(pop3Uri, aLoadInfo, _retval); + } + } + + RefPtr<nsMailboxProtocol> protocol = new nsMailboxProtocol(aURI); + if (!protocol) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = protocol->Initialize(aURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = protocol->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(protocol, _retval); +} + +nsresult nsMailboxService::DisplayMessageForPrinting(const char* aMessageURI, + nsISupports * aDisplayConsumer, + nsIMsgWindow * aMsgWindow, + nsIUrlListener * aUrlListener, + nsIURI ** aURL) +{ + mPrintingOperation = true; + nsresult rv = FetchMessage(aMessageURI, aDisplayConsumer, aMsgWindow,aUrlListener, nullptr, + nsIMailboxUrl::ActionFetchMessage, nullptr, aURL); + mPrintingOperation = false; + return rv; +} + +NS_IMETHODIMP nsMailboxService::Search(nsIMsgSearchSession *aSearchSession, nsIMsgWindow *aMsgWindow, nsIMsgFolder *aMsgFolder, const char *aMessageUri) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsMailboxService::DecomposeMailboxURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey) +{ + NS_ENSURE_ARG_POINTER(aMessageURI); + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aMsgKey); + + nsresult rv = NS_OK; + nsAutoCString folderURI; + rv = nsParseLocalMessageURI(aMessageURI, folderURI, aMsgKey); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFResource> res; + rv = rdf->GetResource(folderURI, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = res->QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) aFolder); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsMailboxService::MessageURIToMsgHdr(const char *uri, nsIMsgDBHdr **_retval) +{ + NS_ENSURE_ARG_POINTER(uri); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = NS_OK; + + nsCOMPtr<nsIMsgFolder> folder; + nsMsgKey msgKey; + + rv = DecomposeMailboxURI(uri, getter_AddRefs(folder), &msgKey); + NS_ENSURE_SUCCESS(rv,rv); + + rv = folder->GetMessageHeader(msgKey, _retval); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} diff --git a/mailnews/local/src/nsMailboxService.h b/mailnews/local/src/nsMailboxService.h new file mode 100644 index 000000000..c7fc759ee --- /dev/null +++ b/mailnews/local/src/nsMailboxService.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsMailboxService_h___ +#define nsMailboxService_h___ + +#include "nscore.h" +#include "nsISupports.h" + +#include "nsIMailboxService.h" +#include "nsIMsgMessageService.h" +#include "nsIMailboxUrl.h" +#include "nsIURL.h" +#include "nsIUrlListener.h" +#include "nsIStreamListener.h" +#include "nsIFile.h" +#include "nsIProtocolHandler.h" +#include "nsIRDFService.h" + +class nsMailboxService : public nsIMailboxService, public nsIMsgMessageService, public nsIMsgMessageFetchPartService, public nsIProtocolHandler +{ +public: + + nsMailboxService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMAILBOXSERVICE + NS_DECL_NSIMSGMESSAGESERVICE + NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE + NS_DECL_NSIPROTOCOLHANDLER + +protected: + virtual ~nsMailboxService(); + bool mPrintingOperation; + + // helper functions used by the service + nsresult PrepareMessageUrl(const char * aSrcMsgMailboxURI, nsIUrlListener * aUrlListener, + nsMailboxAction aMailboxAction, nsIMailboxUrl ** aMailboxUrl, + nsIMsgWindow *msgWindow); + + nsresult RunMailboxUrl(nsIURI * aMailboxUrl, nsISupports * aDisplayConsumer = nullptr); + + nsresult FetchMessage(const char* aMessageURI, + nsISupports * aDisplayConsumer, + nsIMsgWindow * aMsgWindow, + nsIUrlListener * aUrlListener, + const char * aFileName, /* only used by open attachment */ + nsMailboxAction mailboxAction, + const char * aCharsetOverride, + nsIURI ** aURL); + + nsresult DecomposeMailboxURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey); +}; + +#endif /* nsMailboxService_h___ */ diff --git a/mailnews/local/src/nsMailboxUrl.cpp b/mailnews/local/src/nsMailboxUrl.cpp new file mode 100644 index 000000000..25fe4f35f --- /dev/null +++ b/mailnews/local/src/nsMailboxUrl.cpp @@ -0,0 +1,556 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... + +#include "nsIURI.h" +#include "nsIMailboxUrl.h" +#include "nsMailboxUrl.h" + +#include "nsStringGlue.h" +#include "nsLocalUtils.h" +#include "nsIMsgDatabase.h" +#include "nsMsgDBCID.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgHdr.h" + +#include "nsIMsgFolder.h" +#include "prprf.h" +#include "prmem.h" +#include "nsIMsgMailSession.h" +#include "nsNetUtil.h" +#include "nsIFileURL.h" + +// this is totally lame and MUST be removed by M6 +// the real fix is to attach the URI to the URL as it runs through netlib +// then grab it and use it on the other side +#include "nsCOMPtr.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +// helper function for parsing the search field of a url +char * extractAttributeValue(const char * searchString, const char * attributeName); + +nsMailboxUrl::nsMailboxUrl() +{ + m_mailboxAction = nsIMailboxUrl::ActionParseMailbox; + m_filePath = nullptr; + m_messageID = nullptr; + m_messageKey = nsMsgKey_None; + m_messageSize = 0; + m_messageFile = nullptr; + m_addDummyEnvelope = false; + m_canonicalLineEnding = false; + m_curMsgIndex = 0; +} + +nsMailboxUrl::~nsMailboxUrl() +{ + PR_Free(m_messageID); +} + +NS_IMPL_ADDREF_INHERITED(nsMailboxUrl, nsMsgMailNewsUrl) +NS_IMPL_RELEASE_INHERITED(nsMailboxUrl, nsMsgMailNewsUrl) + +NS_INTERFACE_MAP_BEGIN(nsMailboxUrl) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMailboxUrl) + NS_INTERFACE_MAP_ENTRY(nsIMailboxUrl) + NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl) + NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl) +NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl) + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsIMailboxUrl specific support +//////////////////////////////////////////////////////////////////////////////////// +nsresult nsMailboxUrl::SetMailboxParser(nsIStreamListener * aMailboxParser) +{ + if (aMailboxParser) + m_mailboxParser = aMailboxParser; + return NS_OK; +} + +nsresult nsMailboxUrl::GetMailboxParser(nsIStreamListener ** aConsumer) +{ + NS_ENSURE_ARG_POINTER(aConsumer); + + NS_IF_ADDREF(*aConsumer = m_mailboxParser); + return NS_OK; +} + +nsresult nsMailboxUrl::SetMailboxCopyHandler(nsIStreamListener * aMailboxCopyHandler) +{ + if (aMailboxCopyHandler) + m_mailboxCopyHandler = aMailboxCopyHandler; + return NS_OK; +} + +nsresult nsMailboxUrl::GetMailboxCopyHandler(nsIStreamListener ** aMailboxCopyHandler) +{ + NS_ENSURE_ARG_POINTER(aMailboxCopyHandler); + + if (aMailboxCopyHandler) + { + *aMailboxCopyHandler = m_mailboxCopyHandler; + NS_IF_ADDREF(*aMailboxCopyHandler); + } + + return NS_OK; +} + +nsresult nsMailboxUrl::GetMessageKey(nsMsgKey* aMessageKey) +{ + *aMessageKey = m_messageKey; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::GetMessageSize(uint32_t * aMessageSize) +{ + if (aMessageSize) + { + *aMessageSize = m_messageSize; + return NS_OK; + } + else + return NS_ERROR_NULL_POINTER; +} + +nsresult nsMailboxUrl::SetMessageSize(uint32_t aMessageSize) +{ + m_messageSize = aMessageSize; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::GetPrincipalSpec(nsACString& aPrincipalSpec) +{ + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsURL; + QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL)); + + nsAutoCString spec; + mailnewsURL->GetSpecIgnoringRef(spec); + + // mailbox: URLs contain a lot of query parts. We want need a normalised form: + // mailbox:///path/to/folder?number=nn. + // We also need to translate the second form mailbox://user@domain@server/folder?number=nn. + + char* messageKey = extractAttributeValue(spec.get(), "number="); + + // Strip any query part beginning with ? or /; + int32_t ind = spec.Find("/;"); + if (ind != kNotFound) + spec.SetLength(ind); + + ind = spec.FindChar('?'); + if (ind != kNotFound) + spec.SetLength(ind); + + // Check for format lacking absolute path. + if (spec.Find("///") == kNotFound) { + nsCString folderPath; + nsresult rv = nsLocalURI2Path(kMailboxRootURI, spec.get(), folderPath); + if (NS_SUCCEEDED (rv)) { + nsAutoCString buf; + MsgEscapeURL(folderPath, + nsINetUtil::ESCAPE_URL_DIRECTORY | nsINetUtil::ESCAPE_URL_FORCED, buf); + spec = NS_LITERAL_CSTRING("mailbox://") + buf; + } + } + + spec += NS_LITERAL_CSTRING("?number="); + spec.Append(messageKey); + PR_Free(messageKey); + + aPrincipalSpec.Assign(spec); + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::SetUri(const char * aURI) +{ + mURI= aURI; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::CloneInternal(uint32_t aRefHandlingMode, + const nsACString& newRef, + nsIURI **_retval) +{ + nsresult rv = nsMsgMailNewsUrl::CloneInternal(aRefHandlingMode, + newRef, _retval); + NS_ENSURE_SUCCESS(rv, rv); + // also clone the mURI member, because GetUri below won't work if + // mURI isn't set due to nsIFile fun. + nsCOMPtr <nsIMsgMessageUrl> clonedUrl = do_QueryInterface(*_retval); + if (clonedUrl) + clonedUrl->SetUri(mURI.get()); + return rv; +} + +NS_IMETHODIMP nsMailboxUrl::GetUri(char ** aURI) +{ + // if we have been given a uri to associate with this url, then use it + // otherwise try to reconstruct a URI on the fly.... + + if (!mURI.IsEmpty()) + *aURI = ToNewCString(mURI); + else + { + if (m_filePath) + { + nsAutoCString baseUri; + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // we blow off errors here so that we can open attachments + // in .eml files. + (void) accountManager->FolderUriForPath(m_filePath, baseUri); + if (baseUri.IsEmpty()) { + rv = m_baseURL->GetSpec(baseUri); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCString baseMessageURI; + nsCreateLocalBaseMessageURI(baseUri, baseMessageURI); + nsAutoCString uriStr; + nsBuildLocalMessageURI(baseMessageURI.get(), m_messageKey, uriStr); + *aURI = ToNewCString(uriStr); + } + else + *aURI = nullptr; + } + + return NS_OK; +} + +nsresult nsMailboxUrl::GetMsgHdrForKey(nsMsgKey msgKey, nsIMsgDBHdr ** aMsgHdr) +{ + nsresult rv = NS_OK; + if (aMsgHdr && m_filePath) + { + nsCOMPtr<nsIMsgDatabase> mailDBFactory; + nsCOMPtr<nsIMsgDatabase> mailDB; + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + + if (msgDBService) + rv = msgDBService->OpenMailDBFromFile(m_filePath, nullptr, false, + false, getter_AddRefs(mailDB)); + if (NS_SUCCEEDED(rv) && mailDB) // did we get a db back? + rv = mailDB->GetMsgHdrForKey(msgKey, aMsgHdr); + else + { + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (!msgWindow) + { + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + } + + // maybe this is .eml file we're trying to read. See if we can get a header from the header sink. + if (msgWindow) + { + nsCOMPtr<nsIMsgHeaderSink> headerSink; + msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + { + rv = headerSink->GetDummyMsgHeader(aMsgHdr); + if (NS_SUCCEEDED(rv)) + { + int64_t fileSize = 0; + m_filePath->GetFileSize(&fileSize); + (*aMsgHdr)->SetMessageSize(fileSize); + } + } + } + } + } + else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +NS_IMETHODIMP nsMailboxUrl::GetMessageHeader(nsIMsgDBHdr ** aMsgHdr) +{ + if (m_dummyHdr) + { + NS_IF_ADDREF(*aMsgHdr = m_dummyHdr); + return NS_OK; + } + return GetMsgHdrForKey(m_messageKey, aMsgHdr); +} + +NS_IMETHODIMP nsMailboxUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr) +{ + m_dummyHdr = aMsgHdr; + return NS_OK; +} + +NS_IMPL_GETSET(nsMailboxUrl, AddDummyEnvelope, bool, m_addDummyEnvelope) +NS_IMPL_GETSET(nsMailboxUrl, CanonicalLineEnding, bool, m_canonicalLineEnding) + +NS_IMETHODIMP +nsMailboxUrl::GetOriginalSpec(char **aSpec) +{ + if (!aSpec || m_originalSpec.IsEmpty()) + return NS_ERROR_NULL_POINTER; + *aSpec = ToNewCString(m_originalSpec); + return NS_OK; +} + +NS_IMETHODIMP +nsMailboxUrl::SetOriginalSpec(const char *aSpec) +{ + m_originalSpec = aSpec; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::SetMessageFile(nsIFile * aFile) +{ + m_messageFile = aFile; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::GetMessageFile(nsIFile ** aFile) +{ + // why don't we return an error for null aFile? + if (aFile) + NS_IF_ADDREF(*aFile = m_messageFile); + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::IsUrlType(uint32_t type, bool *isType) +{ + NS_ENSURE_ARG(isType); + + switch(type) + { + case nsIMsgMailNewsUrl::eCopy: + *isType = (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage); + break; + case nsIMsgMailNewsUrl::eMove: + *isType = (m_mailboxAction == nsIMailboxUrl::ActionMoveMessage); + break; + case nsIMsgMailNewsUrl::eDisplay: + *isType = (m_mailboxAction == nsIMailboxUrl::ActionFetchMessage || + m_mailboxAction == nsIMailboxUrl::ActionFetchPart); + break; + default: + *isType = false; + }; + + return NS_OK; + +} + +//////////////////////////////////////////////////////////////////////////////////// +// End nsIMailboxUrl specific support +//////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// possible search part phrases include: MessageID=id&number=MessageKey + +nsresult nsMailboxUrl::ParseSearchPart() +{ + nsAutoCString searchPart; + nsresult rv = GetQuery(searchPart); + // add code to this function to decompose everything past the '?'..... + if (NS_SUCCEEDED(rv) && !searchPart.IsEmpty()) + { + // the action for this mailbox must be a display message... + char * msgPart = extractAttributeValue(searchPart.get(), "part="); + if (msgPart) // if we have a part in the url then we must be fetching just the part. + m_mailboxAction = nsIMailboxUrl::ActionFetchPart; + else + m_mailboxAction = nsIMailboxUrl::ActionFetchMessage; + + char * messageKey = extractAttributeValue(searchPart.get(), "number="); + m_messageID = extractAttributeValue(searchPart.get(),"messageid="); + if (messageKey) + m_messageKey = (nsMsgKey) ParseUint64Str(messageKey); // convert to a uint32_t... + + PR_Free(msgPart); + PR_Free(messageKey); + } + else + m_mailboxAction = nsIMailboxUrl::ActionParseMailbox; + + return rv; +} + +// warning: don't assume when parsing the url that the protocol part is "news"... +nsresult nsMailboxUrl::ParseUrl() +{ + GetFilePath(m_file); + + ParseSearchPart(); + // ### fix me. + // this hack is to avoid asserting on every local message loaded because the security manager + // is creating an empty "mailbox://" uri for every message. + if (m_file.Length() < 2) + m_filePath = nullptr; + else + { + nsCString fileUri("file://"); + fileUri.Append(m_file); + nsresult rv; + nsCOMPtr<nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + nsCOMPtr <nsIURI> uri; + rv = ioService->NewURI(fileUri, nullptr, nullptr, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFileURL> fileURL = do_QueryInterface(uri); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFile> fileURLFile; + fileURL->GetFile(getter_AddRefs(fileURLFile)); + m_filePath = do_QueryInterface(fileURLFile, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + GetPath(m_file); + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::SetSpec(const nsACString &aSpec) +{ + nsresult rv = nsMsgMailNewsUrl::SetSpec(aSpec); + if (NS_SUCCEEDED(rv)) + rv = ParseUrl(); + return rv; +} + +NS_IMETHODIMP nsMailboxUrl::SetQuery(const nsACString &aQuery) +{ + nsresult rv = nsMsgMailNewsUrl::SetQuery(aQuery); + if (NS_SUCCEEDED(rv)) + rv = ParseUrl(); + return rv; +} + +// takes a string like ?messageID=fooo&number=MsgKey and returns a new string +// containing just the attribute value. i.e you could pass in this string with +// an attribute name of messageID and I'll return fooo. Use PR_Free to delete +// this string... + +// Assumption: attribute pairs in the string are separated by '&'. +char * extractAttributeValue(const char * searchString, const char * attributeName) +{ + char * attributeValue = nullptr; + + if (searchString && attributeName) + { + // search the string for attributeName + uint32_t attributeNameSize = PL_strlen(attributeName); + char * startOfAttribute = PL_strcasestr(searchString, attributeName); + if (startOfAttribute) + { + startOfAttribute += attributeNameSize; // skip over the attributeName + if (startOfAttribute) // is there something after the attribute name + { + char * endOfAttribute = startOfAttribute ? PL_strchr(startOfAttribute, '&') : nullptr; + nsDependentCString attributeValueStr; + if (startOfAttribute && endOfAttribute) // is there text after attribute value + attributeValueStr.Assign(startOfAttribute, endOfAttribute - startOfAttribute); + else // there is nothing left so eat up rest of line. + attributeValueStr.Assign(startOfAttribute); + + // now unescape the string... + nsCString unescapedValue; + MsgUnescapeString(attributeValueStr, 0, unescapedValue); + attributeValue = PL_strdup(unescapedValue.get()); + } // if we have a attribute value + + } // if we have a attribute name + } // if we got non-null search string and attribute name values + + return attributeValue; +} + +// nsIMsgI18NUrl support + +nsresult nsMailboxUrl::GetFolder(nsIMsgFolder **msgFolder) +{ + // if we have a RDF URI, then try to get the folder for that URI and then ask the folder + // for it's charset.... + nsCString uri; + GetUri(getter_Copies(uri)); + NS_ENSURE_TRUE(!uri.IsEmpty(), NS_ERROR_FAILURE); + nsCOMPtr<nsIMsgDBHdr> msg; + GetMsgDBHdrFromURI(uri.get(), getter_AddRefs(msg)); + if (!msg) + return NS_ERROR_FAILURE; + return msg->GetFolder(msgFolder); +} + +NS_IMETHODIMP nsMailboxUrl::GetFolderCharset(char ** aCharacterSet) +{ + NS_ENSURE_ARG_POINTER(aCharacterSet); + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = GetFolder(getter_AddRefs(folder)); + + // In cases where a file is not associated with a folder, for + // example standalone .eml files, failure is normal. + if (NS_FAILED(rv)) + return rv; + nsCString tmpStr; + folder->GetCharset(tmpStr); + *aCharacterSet = ToNewCString(tmpStr); + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::GetFolderCharsetOverride(bool * aCharacterSetOverride) +{ + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv,rv); + NS_ENSURE_TRUE(folder, NS_ERROR_FAILURE); + folder->GetCharsetOverride(aCharacterSetOverride); + + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::GetCharsetOverRide(char ** aCharacterSet) +{ + if (!mCharsetOverride.IsEmpty()) + *aCharacterSet = ToNewCString(mCharsetOverride); + else + *aCharacterSet = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::SetCharsetOverRide(const char * aCharacterSet) +{ + mCharsetOverride = aCharacterSet; + return NS_OK; +} + +/* void setMoveCopyMsgKeys (out nsMsgKey keysToFlag, in long numKeys); */ +NS_IMETHODIMP nsMailboxUrl::SetMoveCopyMsgKeys(nsMsgKey *keysToFlag, int32_t numKeys) +{ + m_keys.ReplaceElementsAt(0, m_keys.Length(), keysToFlag, numKeys); + if (!m_keys.IsEmpty() && m_messageKey == nsMsgKey_None) + m_messageKey = m_keys[0]; + return NS_OK; +} + +NS_IMETHODIMP nsMailboxUrl::GetMoveCopyMsgHdrForIndex(uint32_t msgIndex, nsIMsgDBHdr **msgHdr) +{ + NS_ENSURE_ARG(msgHdr); + if (msgIndex < m_keys.Length()) + { + nsMsgKey nextKey = m_keys[msgIndex]; + return GetMsgHdrForKey(nextKey, msgHdr); + } + return NS_MSG_MESSAGE_NOT_FOUND; +} + +NS_IMETHODIMP nsMailboxUrl::GetNumMoveCopyMsgs(uint32_t *numMsgs) +{ + NS_ENSURE_ARG(numMsgs); + *numMsgs = m_keys.Length(); + return NS_OK; +} diff --git a/mailnews/local/src/nsMailboxUrl.h b/mailnews/local/src/nsMailboxUrl.h new file mode 100644 index 000000000..63973a916 --- /dev/null +++ b/mailnews/local/src/nsMailboxUrl.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsMailboxUrl_h__ +#define nsMailboxUrl_h__ + +#include "mozilla/Attributes.h" +#include "nsIMailboxUrl.h" +#include "nsMsgMailNewsUrl.h" +#include "nsIStreamListener.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" + +class nsMailboxUrl : public nsIMailboxUrl, public nsMsgMailNewsUrl, public nsIMsgMessageUrl, public nsIMsgI18NUrl +{ +public: + // nsIURI over-ride... + NS_IMETHOD SetSpec(const nsACString &aSpec) override; + NS_IMETHOD SetQuery(const nsACString &aQuery) override; + + // from nsIMailboxUrl: + NS_IMETHOD SetMailboxParser(nsIStreamListener * aConsumer) override; + NS_IMETHOD GetMailboxParser(nsIStreamListener ** aConsumer) override; + NS_IMETHOD SetMailboxCopyHandler(nsIStreamListener * aConsumer) override; + NS_IMETHOD GetMailboxCopyHandler(nsIStreamListener ** aConsumer) override; + + NS_IMETHOD GetMessageKey(nsMsgKey* aMessageKey) override; + NS_IMETHOD GetMessageSize(uint32_t *aMessageSize) override; + NS_IMETHOD SetMessageSize(uint32_t aMessageSize) override; + NS_IMETHOD GetMailboxAction(nsMailboxAction *result) override + { + NS_ENSURE_ARG_POINTER(result); + *result = m_mailboxAction; + return NS_OK; + } + NS_IMETHOD SetMailboxAction(nsMailboxAction aAction) override + { + m_mailboxAction = aAction; + return NS_OK; + } + NS_IMETHOD IsUrlType(uint32_t type, bool *isType) override; + NS_IMETHOD SetMoveCopyMsgKeys(nsMsgKey *keysToFlag, int32_t numKeys) override; + NS_IMETHOD GetMoveCopyMsgHdrForIndex(uint32_t msgIndex, nsIMsgDBHdr **msgHdr) override; + NS_IMETHOD GetNumMoveCopyMsgs(uint32_t *numMsgs) override; + NS_IMETHOD GetCurMoveCopyMsgIndex(uint32_t *result) override + { + NS_ENSURE_ARG_POINTER(result); + *result = m_curMsgIndex; + return NS_OK; + } + NS_IMETHOD SetCurMoveCopyMsgIndex(uint32_t aIndex) override + { + m_curMsgIndex = aIndex; + return NS_OK; + } + + NS_IMETHOD GetFolder(nsIMsgFolder **msgFolder) override; + + // nsIMsgMailNewsUrl override + NS_IMETHOD CloneInternal(uint32_t aRefHandlingMode, + const nsACString& newRef, + nsIURI **_retval) override; + + // nsMailboxUrl + nsMailboxUrl(); + NS_DECL_NSIMSGMESSAGEURL + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMSGI18NURL + +protected: + virtual ~nsMailboxUrl(); + // protocol specific code to parse a url... + virtual nsresult ParseUrl(); + nsresult GetMsgHdrForKey(nsMsgKey msgKey, nsIMsgDBHdr ** aMsgHdr); + + // mailboxurl specific state + nsCOMPtr<nsIStreamListener> m_mailboxParser; + nsCOMPtr<nsIStreamListener> m_mailboxCopyHandler; + + nsMailboxAction m_mailboxAction; // the action this url represents...parse mailbox, display messages, etc. + nsCOMPtr <nsIFile> m_filePath; + char *m_messageID; + uint32_t m_messageSize; + nsMsgKey m_messageKey; + nsCString m_file; + // This is currently only set when we're doing something with a .eml file. + // If that changes, we should change the name of this var. + nsCOMPtr<nsIMsgDBHdr> m_dummyHdr; + + // used by save message to disk + nsCOMPtr<nsIFile> m_messageFile; + bool m_addDummyEnvelope; + bool m_canonicalLineEnding; + nsresult ParseSearchPart(); + + // for multiple msg move/copy + nsTArray<nsMsgKey> m_keys; + int32_t m_curMsgIndex; + + // truncated message support + nsCString m_originalSpec; + nsCString mURI; // the RDF URI associated with this url. + nsCString mCharsetOverride; // used by nsIMsgI18NUrl... +}; + +#endif // nsMailboxUrl_h__ diff --git a/mailnews/local/src/nsMovemailIncomingServer.cpp b/mailnews/local/src/nsMovemailIncomingServer.cpp new file mode 100644 index 000000000..cee915e25 --- /dev/null +++ b/mailnews/local/src/nsMovemailIncomingServer.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsMsgLocalCID.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMovemailService.h" +#include "nsIFile.h" +#include "msgCore.h" // pre-compiled headers +#include "nsMovemailIncomingServer.h" +#include "nsServiceManagerUtils.h" + + +static NS_DEFINE_CID(kCMovemailServiceCID, NS_MOVEMAILSERVICE_CID); + + +NS_IMPL_ISUPPORTS_INHERITED(nsMovemailIncomingServer, + nsMsgIncomingServer, + nsIMovemailIncomingServer, + nsILocalMailIncomingServer) + + + +nsMovemailIncomingServer::nsMovemailIncomingServer() +{ + m_canHaveFilters = true; +} + +nsMovemailIncomingServer::~nsMovemailIncomingServer() +{ +} + +NS_IMETHODIMP +nsMovemailIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + nsCOMPtr<nsIMovemailService> movemailService(do_GetService( + kCMovemailServiceCID, &rv)); + if (NS_FAILED(rv)) return rv; + nsCOMPtr<nsIMsgFolder> inbox; + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + nsCOMPtr<nsIUrlListener> urlListener; + rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + if(NS_SUCCEEDED(rv) && rootMsgFolder) + { + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + if (!inbox) return NS_ERROR_FAILURE; + } + + SetPerformingBiff(true); + urlListener = do_QueryInterface(inbox); + + bool downloadOnBiff = false; + rv = GetDownloadOnBiff(&downloadOnBiff); + if (downloadOnBiff) + { + nsCOMPtr <nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, + &rv); + if (localInbox && NS_SUCCEEDED(rv)) + { + bool valid = false; + nsCOMPtr <nsIMsgDatabase> db; + rv = inbox->GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + { + rv = db->GetSummaryValid(&valid); + } + if (NS_SUCCEEDED(rv) && valid) + { + rv = movemailService->GetNewMail(aMsgWindow, urlListener, inbox, + this, nullptr); + } + else + { + bool isLocked; + inbox->GetLocked(&isLocked); + if (!isLocked) + { + rv = localInbox->ParseFolder(aMsgWindow, urlListener); + } + if (NS_SUCCEEDED(rv)) + { + rv = localInbox->SetCheckForNewMessagesAfterParsing(true); + } + } + } + } + else + { + movemailService->CheckForNewMail(urlListener, inbox, this, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailIncomingServer::SetFlagsOnDefaultMailboxes() +{ + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(rootFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse); +} + +NS_IMETHODIMP nsMovemailIncomingServer::CreateDefaultMailboxes() +{ + nsresult rv = CreateLocalFolder(NS_LITERAL_STRING("Inbox")); + NS_ENSURE_SUCCESS(rv, rv); + + return CreateLocalFolder(NS_LITERAL_STRING("Trash")); +} + + +NS_IMETHODIMP +nsMovemailIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener, + nsIMsgFolder *aMsgFolder, + nsIURI **aResult) +{ + nsresult rv; + + nsCOMPtr<nsIMovemailService> movemailService = + do_GetService(kCMovemailServiceCID, &rv); + + if (NS_FAILED(rv)) return rv; + + rv = movemailService->GetNewMail(aMsgWindow, aUrlListener, + aMsgFolder, this, aResult); + + return rv; +} + +NS_IMETHODIMP +nsMovemailIncomingServer::GetDownloadMessagesAtStartup(bool *getMessagesAtStartup) +{ + NS_ENSURE_ARG_POINTER(getMessagesAtStartup); + *getMessagesAtStartup = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailIncomingServer::GetCanBeDefaultServer(bool *aCanBeDefaultServer) +{ + NS_ENSURE_ARG_POINTER(aCanBeDefaultServer); + *aCanBeDefaultServer = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailIncomingServer::GetCanSearchMessages(bool *canSearchMessages) +{ + NS_ENSURE_ARG_POINTER(canSearchMessages); + *canSearchMessages = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) +{ + NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff); + *aServerRequiresPasswordForBiff = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailIncomingServer::GetAccountManagerChrome(nsAString& aResult) +{ + aResult.AssignLiteral("am-main.xul"); + return NS_OK; +} diff --git a/mailnews/local/src/nsMovemailIncomingServer.h b/mailnews/local/src/nsMovemailIncomingServer.h new file mode 100644 index 000000000..7fefec965 --- /dev/null +++ b/mailnews/local/src/nsMovemailIncomingServer.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef __nsMovemailIncomingServer_h +#define __nsMovemailIncomingServer_h + +#include "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsIMovemailIncomingServer.h" +#include "nsILocalMailIncomingServer.h" +#include "nsMailboxServer.h" + +/* get some implementation from nsMsgIncomingServer */ +class nsMovemailIncomingServer : public nsMailboxServer, + public nsIMovemailIncomingServer, + public nsILocalMailIncomingServer + +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMOVEMAILINCOMINGSERVER + NS_DECL_NSILOCALMAILINCOMINGSERVER + + nsMovemailIncomingServer(); + + NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override; + NS_IMETHOD GetDownloadMessagesAtStartup(bool *getMessages) override; + NS_IMETHOD GetCanBeDefaultServer(bool *canBeDefaultServer) override; + NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override; + NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override; + NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override; + +private: + virtual ~nsMovemailIncomingServer(); +}; + + +#endif diff --git a/mailnews/local/src/nsMovemailService.cpp b/mailnews/local/src/nsMovemailService.cpp new file mode 100644 index 000000000..a4280c975 --- /dev/null +++ b/mailnews/local/src/nsMovemailService.cpp @@ -0,0 +1,694 @@ +/* -*- 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 <unistd.h> // for link(), used in spool-file locking + +#include "prenv.h" +#include "private/pprio.h" // for our kernel-based locking +#include "nspr.h" + +#include "msgCore.h" // precompiled header... + +#include "nsMovemailService.h" +#include "nsIMovemailService.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMovemailIncomingServer.h" +#include "nsIMsgProtocolInfo.h" +#include "nsParseMailbox.h" +#include "nsIMsgFolder.h" +#include "nsIPrompt.h" + +#include "nsIFile.h" +#include "nsMailDirServiceDefs.h" +#include "nsMsgUtils.h" + +#include "nsCOMPtr.h" +#include "nsMsgFolderFlags.h" + +#include "nsILineInputStream.h" +#include "nsISeekableStream.h" +#include "nsNetUtil.h" +#include "nsAutoPtr.h" +#include "nsIStringBundle.h" +#include "nsIMsgPluggableStore.h" +#include "mozilla/Services.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" + +#include "mozilla/Logging.h" +#if defined(PR_LOGGING) +// +// export NSPR_LOG_MODULES=Movemail:5 +// +static PRLogModuleInfo *gMovemailLog = nullptr; +#define LOG(args) MOZ_LOG(gMovemailLog, mozilla::LogLevel::Debug, args) +#else +#define LOG(args) +#endif + +#define PREF_MAIL_ROOT_MOVEMAIL "mail.root.movemail" // old - for backward compatibility only +#define PREF_MAIL_ROOT_MOVEMAIL_REL "mail.root.movemail-rel" + +#define LOCK_SUFFIX ".lock" +#define MOZLOCK_SUFFIX ".mozlock" + +const char * gDefaultSpoolPaths[] = { + "/var/spool/mail/", + "/usr/spool/mail/", + "/var/mail/", + "/usr/mail/" +}; +#define NUM_DEFAULT_SPOOL_PATHS (sizeof(gDefaultSpoolPaths)/sizeof(gDefaultSpoolPaths[0])) + +namespace { +class MOZ_STACK_CLASS SpoolLock +{ +public: + /** + * Try to create a lock for the spool file while we operate on it. + * + * @param aSpoolName The path to the spool file. + * @param aSeconds The number of seconds to retry the locking. + * @param aMovemail The movemail service requesting the lock. + * @param aServer The nsIMsgIncomingServer requesting the lock. + */ + SpoolLock(nsACString *aSpoolPath, int aSeconds, nsMovemailService &aMovemail, + nsIMsgIncomingServer *aServer); + + ~SpoolLock(); + + bool isLocked(); + +private: + bool mLocked; + nsCString mSpoolName; + bool mUsingLockFile; + RefPtr<nsMovemailService> mOwningService; + nsCOMPtr<nsIMsgIncomingServer> mServer; + + bool ObtainSpoolLock(unsigned int aSeconds); + bool YieldSpoolLock(); +}; + +} + +nsMovemailService::nsMovemailService() +{ +#if defined(PR_LOGGING) + if (!gMovemailLog) + gMovemailLog = PR_NewLogModule("Movemail"); +#endif + LOG(("nsMovemailService created: 0x%x\n", this)); +} + +nsMovemailService::~nsMovemailService() +{} + + +NS_IMPL_ISUPPORTS(nsMovemailService, + nsIMovemailService, + nsIMsgProtocolInfo) + + +NS_IMETHODIMP +nsMovemailService::CheckForNewMail(nsIUrlListener * aUrlListener, + nsIMsgFolder *inbox, + nsIMovemailIncomingServer *movemailServer, + nsIURI ** aURL) +{ + nsresult rv = NS_OK; + LOG(("nsMovemailService::CheckForNewMail\n")); + return rv; +} + +void +nsMovemailService::Error(const char* errorCode, + const char16_t **params, + uint32_t length) +{ + if (!mMsgWindow) return; + + nsCOMPtr<nsIPrompt> dialog; + nsresult rv = mMsgWindow->GetPromptDialog(getter_AddRefs(dialog)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return; + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + return; + + nsString errStr; + // Format the error string if necessary + if (params) + bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(errorCode).get(), + params, length, getter_Copies(errStr)); + else + bundle->GetStringFromName(NS_ConvertASCIItoUTF16(errorCode).get(), + getter_Copies(errStr)); + + if (!errStr.IsEmpty()) { + dialog->Alert(nullptr, errStr.get()); + } +} + +SpoolLock::SpoolLock(nsACString *aSpoolName, int aSeconds, + nsMovemailService &aMovemail, + nsIMsgIncomingServer *aServer) +: mLocked(false), + mSpoolName(*aSpoolName), + mOwningService(&aMovemail), + mServer(aServer) +{ + if (!ObtainSpoolLock(aSeconds)) { + NS_ConvertUTF8toUTF16 lockFile(mSpoolName); + lockFile.AppendLiteral(LOCK_SUFFIX); + const char16_t* params[] = { lockFile.get() }; + mOwningService->Error("movemailCantCreateLock", params, 1); + return; + } + mServer->SetServerBusy(true); + mLocked = true; +} + +SpoolLock::~SpoolLock() { + if (mLocked && !YieldSpoolLock()) { + NS_ConvertUTF8toUTF16 lockFile(mSpoolName); + lockFile.AppendLiteral(LOCK_SUFFIX); + const char16_t* params[] = { lockFile.get() }; + mOwningService->Error("movemailCantDeleteLock", params, 1); + } + mServer->SetServerBusy(false); +} + +bool +SpoolLock::isLocked() { + return mLocked; +} + +bool +SpoolLock::ObtainSpoolLock(unsigned int aSeconds /* number of seconds to retry */) +{ + /* + * Locking procedures: + * If the directory is not writable, we want to use the appropriate system + * utilites to lock the file. + * If the directory is writable, we want to go through the create-and-link + * locking procedures to make it atomic for certain networked file systems. + * This involves creating a .mozlock file and attempting to hard-link it to + * the customary .lock file. + */ + nsCOMPtr<nsIFile> spoolFile; + nsresult rv = NS_NewNativeLocalFile(mSpoolName, + true, + getter_AddRefs(spoolFile)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIFile> directory; + rv = spoolFile->GetParent(getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, false); + + rv = directory->IsWritable(&mUsingLockFile); + NS_ENSURE_SUCCESS(rv, false); + + if (!mUsingLockFile) { + LOG(("Attempting to use kernel file lock")); + PRFileDesc *fd; + rv = spoolFile->OpenNSPRFileDesc(PR_RDWR, 0, &fd); + NS_ENSURE_SUCCESS(rv, false); + PRStatus lock_result; + unsigned int retry_count = 0; + + do { + lock_result = PR_TLockFile(fd); + + retry_count++; + LOG(("Attempt %d of %d to lock file", retry_count, aSeconds)); + if (aSeconds > 0 && lock_result == PR_FAILURE) { + // pause 1sec, waiting for .lock to go away + PRIntervalTime sleepTime = 1000; // 1 second + PR_Sleep(sleepTime); + } + } while (lock_result == PR_FAILURE && retry_count < aSeconds); + LOG(("Lock result: %d", lock_result)); + PR_Close(fd); + return lock_result == PR_SUCCESS; + } + // How to lock using files: + // step 1: create SPOOLNAME.mozlock + // 1a: can remove it if it already exists (probably crash-droppings) + // step 2: hard-link SPOOLNAME.mozlock to SPOOLNAME.lock for NFS atomicity + // 2a: if SPOOLNAME.lock is >60sec old then nuke it from orbit + // 2b: repeat step 2 until retry-count expired or hard-link succeeds + // step 3: remove SPOOLNAME.mozlock + // step 4: If step 2 hard-link failed, fail hard; we do not hold the lock + // DONE. + // + // (step 2a not yet implemented) + + nsAutoCString mozlockstr(mSpoolName); + mozlockstr.AppendLiteral(MOZLOCK_SUFFIX); + nsAutoCString lockstr(mSpoolName); + lockstr.AppendLiteral(LOCK_SUFFIX); + + // Create nsIFile for the spool.mozlock file + nsCOMPtr<nsIFile> tmplocfile; + rv = NS_NewNativeLocalFile(mozlockstr, true, getter_AddRefs(tmplocfile)); + NS_ENSURE_SUCCESS(rv, false); + + // THOUGHT: hmm, perhaps use MakeUnique to generate us a unique mozlock? + // ... perhaps not, MakeUnique implementation looks racey -- use mktemp()? + + // step 1: create SPOOLNAME.mozlock + rv = tmplocfile->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { + // can't create our .mozlock file... game over already + LOG(("Failed to create file %s\n", mozlockstr.get())); + return false; + } + + // step 2: hard-link .mozlock file to .lock file (this wackiness + // is necessary for non-racey locking on NFS-mounted spool dirs) + // n.b. XPCOM utilities don't support hard-linking yet, so we + // skip out to <unistd.h> and the POSIX interface for link() + int link_result = 0; + unsigned int retry_count = 0; + + do { + link_result = link(mozlockstr.get(), lockstr.get()); + + retry_count++; + LOG(("Attempt %d of %d to create lock file", retry_count, aSeconds)); + + if (aSeconds > 0 && link_result == -1) { + // pause 1sec, waiting for .lock to go away + PRIntervalTime sleepTime = 1000; // 1 second + PR_Sleep(sleepTime); + } + } while (link_result == -1 && retry_count < aSeconds); + LOG(("Link result: %d", link_result)); + + // step 3: remove .mozlock file, in any case + rv = tmplocfile->Remove(false /* non-recursive */); + if (NS_FAILED(rv)) { + // Could not delete our .mozlock file... very unusual, but + // not fatal. + LOG(("Unable to delete %s", mozlockstr.get())); + } + + // step 4: now we know whether we succeeded or failed + return link_result == 0; +} + + +/** + * Remove our mail-spool-file lock (n.b. we should only try this if + * we're the ones who made the lock in the first place! I.e. if mLocked is true.) + */ +bool +SpoolLock::YieldSpoolLock() +{ + LOG(("YieldSpoolLock(%s)", mSpoolName.get())); + + if (!mUsingLockFile) { + nsCOMPtr<nsIFile> spoolFile; + nsresult rv = NS_NewNativeLocalFile(mSpoolName, + true, + getter_AddRefs(spoolFile)); + NS_ENSURE_SUCCESS(rv, false); + + PRFileDesc *fd; + rv = spoolFile->OpenNSPRFileDesc(PR_RDWR, 0, &fd); + NS_ENSURE_SUCCESS(rv, false); + + bool unlockSucceeded = PR_UnlockFile(fd) == PR_SUCCESS; + PR_Close(fd); + if (unlockSucceeded) + LOG(("YieldSpoolLock was successful.")); + return unlockSucceeded; + } + + nsAutoCString lockstr(mSpoolName); + lockstr.AppendLiteral(LOCK_SUFFIX); + + nsresult rv; + + // Create nsIFile for the spool.lock file + nsCOMPtr<nsIFile> locklocfile; + rv = NS_NewNativeLocalFile(lockstr, true, getter_AddRefs(locklocfile)); + NS_ENSURE_SUCCESS(rv, false); + + // Check if the lock file exists + bool exists; + rv = locklocfile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, false); + + // Delete the file if it exists + if (exists) { + rv = locklocfile->Remove(false /* non-recursive */); + NS_ENSURE_SUCCESS(rv, false); + } + + LOG(("YieldSpoolLock was successful.")); + + // Success. + return true; +} + +static nsresult +LocateSpoolFile(nsACString & spoolPath) +{ + bool isFile; + nsresult rv; + + nsCOMPtr<nsIFile> spoolFile; + rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(spoolFile)); + NS_ENSURE_SUCCESS(rv, rv); + + char * mailEnv = PR_GetEnv("MAIL"); + char * userEnv = PR_GetEnv("USER"); + if (!userEnv) + userEnv = PR_GetEnv("USERNAME"); + + if (mailEnv) { + rv = spoolFile->InitWithNativePath(nsDependentCString(mailEnv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = spoolFile->IsFile(&isFile); + if (NS_SUCCEEDED(rv) && isFile) + spoolPath = mailEnv; + } + else if (userEnv) { + // Try to build the mailbox path from the username and a number + // of guessed spool directory paths. + nsAutoCString tmpPath; + uint32_t i; + for (i = 0; i < NUM_DEFAULT_SPOOL_PATHS; i++) { + tmpPath = gDefaultSpoolPaths[i]; + tmpPath += userEnv; + rv = spoolFile->InitWithNativePath(tmpPath); + NS_ENSURE_SUCCESS(rv, rv); + rv = spoolFile->IsFile(&isFile); + if (NS_SUCCEEDED(rv) && isFile) { + spoolPath = tmpPath; + break; + } + } + } + + return rv; +} + +nsresult +nsMovemailService::GetNewMail(nsIMsgWindow *aMsgWindow, + nsIUrlListener* /* aUrlListener */, + nsIMsgFolder* /* aMsgFolder */, + nsIMovemailIncomingServer *aMovemailServer, + nsIURI ** /* aURL */) +{ + LOG(("nsMovemailService::GetNewMail")); + + NS_ENSURE_ARG_POINTER(aMovemailServer); + // It is OK if aMsgWindow is null. + mMsgWindow = aMsgWindow; + + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> in_server = + do_QueryInterface(aMovemailServer, &rv); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in_server, + NS_MSG_INVALID_OR_MISSING_SERVER); + + // Attempt to locate the mail spool file + nsAutoCString spoolPath; + rv = in_server->GetCharValue("spoolDir", spoolPath); + if (NS_FAILED(rv) || spoolPath.IsEmpty()) + rv = LocateSpoolFile(spoolPath); + if (NS_FAILED(rv) || spoolPath.IsEmpty()) { + Error("movemailSpoolFileNotFound", nullptr, 0); + return NS_ERROR_FAILURE; + } + + NS_ConvertUTF8toUTF16 wideSpoolPath(spoolPath); + const char16_t* spoolPathString[] = { wideSpoolPath.get() }; + + // Create an input stream for the spool file + nsCOMPtr<nsIFile> spoolFile; + rv = NS_NewNativeLocalFile(spoolPath, true, getter_AddRefs(spoolFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIInputStream> spoolInputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(spoolInputStream), spoolFile); + if (NS_FAILED(rv)) { + Error("movemailCantOpenSpoolFile", spoolPathString, 1); + return rv; + } + + // Get a line input interface for the spool file + nsCOMPtr<nsILineInputStream> lineInputStream = + do_QueryInterface(spoolInputStream, &rv); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && lineInputStream, rv); + + nsCOMPtr<nsIMsgFolder> serverFolder; + nsCOMPtr<nsIMsgFolder> inbox; + + rv = in_server->GetRootFolder(getter_AddRefs(serverFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = serverFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && inbox, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = in_server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + // create a new mail parser + RefPtr<nsParseNewMailState> newMailParser = new nsParseNewMailState; + + // Try and obtain the lock for the spool file. + SpoolLock lock(&spoolPath, 5, *this, in_server); + if (!lock.isLocked()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgDBHdr> newHdr; + nsCOMPtr<nsIOutputStream> outputStream; + + // MIDDLE of the FUN : consume the mailbox data. + bool isMore = true; + nsAutoCString buffer; + uint32_t bytesWritten = 0; + while (isMore && + NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) + { + // If first string is empty and we're now at EOF then abort parsing. + if (buffer.IsEmpty() && !isMore && !bytesWritten) { + LOG(("Empty spool file")); + break; + } + + buffer.AppendLiteral(MSG_LINEBREAK); + + if (isMore && StringBeginsWith(buffer, NS_LITERAL_CSTRING("From "))) { + // Finish previous header and message, if any. + if (newHdr) { + outputStream->Flush(); + newMailParser->PublishMsgHeader(nullptr); + rv = msgStore->FinishNewMessage(outputStream, newHdr); + NS_ENSURE_SUCCESS(rv, rv); + newMailParser->Clear(); + } + bool reusable; + rv = msgStore->GetNewMsgOutputStream(inbox, getter_AddRefs(newHdr), + &reusable, getter_AddRefs(outputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = newMailParser->Init(serverFolder, inbox, + nullptr, newHdr, outputStream); + NS_ENSURE_SUCCESS(rv, rv); + } + if (!outputStream) { + // If we do not have outputStream here, something bad happened. + // We probably didn't find the proper message start delimiter "From " + // and are now reading in the middle of a message. Bail out. + Error("movemailCantParseSpool", spoolPathString, 1); + return NS_ERROR_UNEXPECTED; + } + + newMailParser->HandleLine(buffer.BeginWriting(), buffer.Length()); + rv = outputStream->Write(buffer.get(), buffer.Length(), &bytesWritten); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (bytesWritten == buffer.Length()), + NS_ERROR_FAILURE); + + // "From " lines delimit messages, start a new one here. + if (isMore && StringBeginsWith(buffer, NS_LITERAL_CSTRING("From "))) { + buffer.AssignLiteral("X-Mozilla-Status: 8000" MSG_LINEBREAK); + newMailParser->HandleLine(buffer.BeginWriting(), buffer.Length()); + rv = outputStream->Write(buffer.get(), buffer.Length(), &bytesWritten); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (bytesWritten == buffer.Length()), + NS_ERROR_FAILURE); + + buffer.AssignLiteral("X-Mozilla-Status2: 00000000" MSG_LINEBREAK); + newMailParser->HandleLine(buffer.BeginWriting(), buffer.Length()); + rv = outputStream->Write(buffer.get(), buffer.Length(), &bytesWritten); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (bytesWritten == buffer.Length()), + NS_ERROR_FAILURE); + } + } + if (outputStream) { + outputStream->Flush(); + newMailParser->PublishMsgHeader(nullptr); + newMailParser->OnStopRequest(nullptr, nullptr, NS_OK); + rv = msgStore->FinishNewMessage(outputStream, newHdr); + NS_ENSURE_SUCCESS(rv, rv); + outputStream->Close(); + } + // Truncate the spool file as we parsed it successfully. + rv = spoolFile->SetFileSize(0); + if (NS_FAILED(rv)) { + Error("movemailCantTruncateSpoolFile", spoolPathString, 1); + } + + LOG(("GetNewMail returning rv=%d", rv)); + return rv; +} + + +NS_IMETHODIMP +nsMovemailService::SetDefaultLocalPath(nsIFile *aPath) +{ + NS_ENSURE_ARG(aPath); + return NS_SetPersistentFile(PREF_MAIL_ROOT_MOVEMAIL_REL, PREF_MAIL_ROOT_MOVEMAIL, aPath); +} + +NS_IMETHODIMP +nsMovemailService::GetDefaultLocalPath(nsIFile ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + nsresult rv; + bool havePref; + nsCOMPtr<nsIFile> localFile; + rv = NS_GetPersistentFile(PREF_MAIL_ROOT_MOVEMAIL_REL, + PREF_MAIL_ROOT_MOVEMAIL, + NS_APP_MAIL_50_DIR, + havePref, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) return rv; + + bool exists; + rv = localFile->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + + if (!havePref || !exists) { + rv = NS_SetPersistentFile(PREF_MAIL_ROOT_MOVEMAIL_REL, PREF_MAIL_ROOT_MOVEMAIL, localFile); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref."); + } + + NS_IF_ADDREF(*aResult = localFile); + return NS_OK; +} + + +NS_IMETHODIMP +nsMovemailService::GetServerIID(nsIID* *aServerIID) +{ + *aServerIID = new nsIID(NS_GET_IID(nsIMovemailIncomingServer)); + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetRequiresUsername(bool *aRequiresUsername) +{ + NS_ENSURE_ARG_POINTER(aRequiresUsername); + *aRequiresUsername = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress) +{ + NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress); + *aPreflightPrettyNameWithEmailAddress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp) +{ + NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp); + *aCanLoginAtStartUp = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetCanDelete(bool *aCanDelete) +{ + NS_ENSURE_ARG_POINTER(aCanDelete); + *aCanDelete = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetCanGetMessages(bool *aCanGetMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetMessages); + *aCanGetMessages = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages); + *aCanGetIncomingMessages = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetCanDuplicate(bool *aCanDuplicate) +{ + NS_ENSURE_ARG_POINTER(aCanDuplicate); + *aCanDuplicate = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetDefaultDoBiff(bool *aDoBiff) +{ + NS_ENSURE_ARG_POINTER(aDoBiff); + // by default, do biff for movemail + *aDoBiff = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetDefaultServerPort(bool isSecure, int32_t *aDefaultPort) +{ + NS_ENSURE_ARG_POINTER(aDefaultPort); + *aDefaultPort = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetShowComposeMsgLink(bool *showComposeMsgLink) +{ + NS_ENSURE_ARG_POINTER(showComposeMsgLink); + *showComposeMsgLink = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMovemailService::GetFoldersCreatedAsync(bool *aAsyncCreation) +{ + NS_ENSURE_ARG_POINTER(aAsyncCreation); + *aAsyncCreation = false; + return NS_OK; +} diff --git a/mailnews/local/src/nsMovemailService.h b/mailnews/local/src/nsMovemailService.h new file mode 100644 index 000000000..5b1ae1053 --- /dev/null +++ b/mailnews/local/src/nsMovemailService.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef nsMovemailService_h___ +#define nsMovemailService_h___ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIMovemailService.h" +#include "nsIMsgProtocolInfo.h" +#include "nsIMsgWindow.h" + +class nsMovemailService : public nsIMsgProtocolInfo, public nsIMovemailService +{ +public: + nsMovemailService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMOVEMAILSERVICE + NS_DECL_NSIMSGPROTOCOLINFO + + void Error(const char* errorCode, const char16_t **params, uint32_t length); + +private: + virtual ~nsMovemailService(); + nsCOMPtr<nsIMsgWindow> mMsgWindow; +}; + +#endif /* nsMovemailService_h___ */ diff --git a/mailnews/local/src/nsMsgBrkMBoxStore.cpp b/mailnews/local/src/nsMsgBrkMBoxStore.cpp new file mode 100644 index 000000000..6eb3063ad --- /dev/null +++ b/mailnews/local/src/nsMsgBrkMBoxStore.cpp @@ -0,0 +1,1124 @@ +/* -*- 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/. */ + +/** + Class for handling Berkeley Mailbox stores. +*/ + +#include "prlog.h" +#include "msgCore.h" +#include "nsMsgBrkMBoxStore.h" +#include "nsIMsgFolder.h" +#include "nsMsgFolderFlags.h" +#include "nsILocalMailIncomingServer.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsCOMArray.h" +#include "nsIFile.h" +#include "nsIMsgHdr.h" +#include "nsNetUtil.h" +#include "nsIMsgDatabase.h" +#include "nsNativeCharsetUtils.h" +#include "nsMsgUtils.h" +#include "nsMsgDBCID.h" +#include "nsIDBFolderInfo.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMailHeaders.h" +#include "nsReadLine.h" +#include "nsParseMailbox.h" +#include "nsIMailboxService.h" +#include "nsMsgLocalCID.h" +#include "nsIMsgFolderCompactor.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "mozilla/Preferences.h" +#include "prprf.h" +#include <cstdlib> // for std::abs(int/long) +#include <cmath> // for std::abs(float/double) + +nsMsgBrkMBoxStore::nsMsgBrkMBoxStore() +{ +} + +nsMsgBrkMBoxStore::~nsMsgBrkMBoxStore() +{ +} + +NS_IMPL_ISUPPORTS(nsMsgBrkMBoxStore, nsIMsgPluggableStore) + +NS_IMETHODIMP nsMsgBrkMBoxStore::DiscoverSubFolders(nsIMsgFolder *aParentFolder, + bool aDeep) +{ + NS_ENSURE_ARG_POINTER(aParentFolder); + + nsCOMPtr<nsIFile> path; + nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) + return rv; + + bool exists; + path->Exists(&exists); + if (!exists) { + rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + return AddSubFolders(aParentFolder, path, aDeep); +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::CreateFolder(nsIMsgFolder *aParent, + const nsAString &aFolderName, + nsIMsgFolder **aResult) +{ + NS_ENSURE_ARG_POINTER(aParent); + NS_ENSURE_ARG_POINTER(aResult); + if (aFolderName.IsEmpty()) + return NS_MSG_ERROR_INVALID_FOLDER_NAME; + + nsCOMPtr<nsIFile> path; + nsCOMPtr<nsIMsgFolder> child; + nsresult rv = aParent->GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) + return rv; + //Get a directory based on our current path. + rv = CreateDirectoryForFolder(path); + if (NS_FAILED(rv)) + return rv; + + // Now we have a valid directory or we have returned. + // Make sure the new folder name is valid + nsAutoString safeFolderName(aFolderName); + NS_MsgHashIfNecessary(safeFolderName); + + path->Append(safeFolderName); + bool exists; + path->Exists(&exists); + if (exists) //check this because localized names are different from disk names + return NS_MSG_FOLDER_EXISTS; + + rv = path->Create(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + //GetFlags and SetFlags in AddSubfolder will fail because we have no db at + // this point but mFlags is set. + rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child)); + if (!child || NS_FAILED(rv)) + { + path->Remove(false); + return rv; + } + // Create an empty database for this mail folder, set its name from the user + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + nsCOMPtr<nsIMsgDatabase> unusedDB; + rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB)); + + if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) && + unusedDB) + { + //need to set the folder name + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (NS_SUCCEEDED(rv)) + folderInfo->SetMailboxName(safeFolderName); + + unusedDB->SetSummaryValid(true); + unusedDB->Close(true); + aParent->UpdateSummaryTotals(true); + } + else + { + path->Remove(false); + rv = NS_MSG_CANT_CREATE_FOLDER; + } + } + child.forget(aResult); + return rv; +} + +// Get the current attributes of the mbox file, corrected for caching +void nsMsgBrkMBoxStore::GetMailboxModProperties(nsIMsgFolder *aFolder, + int64_t *aSize, uint32_t *aDate) +{ + // We'll simply return 0 on errors. + *aDate = 0; + *aSize = 0; + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = pathFile->GetFileSize(aSize); + if (NS_FAILED(rv)) + return; // expected result for virtual folders + + PRTime lastModTime; + rv = pathFile->GetLastModifiedTime(&lastModTime); + NS_ENSURE_SUCCESS_VOID(rv); + + *aDate = (uint32_t) (lastModTime / PR_MSEC_PER_SEC); +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::HasSpaceAvailable(nsIMsgFolder *aFolder, + int64_t aSpaceRequested, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aFolder); + + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + bool allow4GBfolders = mozilla::Preferences::GetBool("mailnews.allowMboxOver4GB", true); + + if (!allow4GBfolders) { + // Allow the mbox to only reach 0xFFC00000 = 4 GiB - 4 MiB. + int64_t fileSize; + rv = pathFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = ((fileSize + aSpaceRequested) < 0xFFC00000LL); + if (!*aResult) + return NS_ERROR_FILE_TOO_BIG; + } + + *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested); + if (!*aResult) + return NS_ERROR_FILE_DISK_FULL; + + return NS_OK; +} + +static bool gGotGlobalPrefs = false; +static int32_t gTimeStampLeeway = 60; + +NS_IMETHODIMP nsMsgBrkMBoxStore::IsSummaryFileValid(nsIMsgFolder *aFolder, + nsIMsgDatabase *aDB, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aDB); + NS_ENSURE_ARG_POINTER(aResult); + // We only check local folders for db validity. + nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(aFolder)); + if (!localFolder) + { + *aResult = true; + return NS_OK; + } + + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = aDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + NS_ENSURE_SUCCESS(rv, rv); + int64_t folderSize; + uint32_t folderDate; + int32_t numUnreadMessages; + + *aResult = false; + + folderInfo->GetNumUnreadMessages(&numUnreadMessages); + folderInfo->GetFolderSize(&folderSize); + folderInfo->GetFolderDate(&folderDate); + + int64_t fileSize = 0; + uint32_t actualFolderTimeStamp = 0; + GetMailboxModProperties(aFolder, &fileSize, &actualFolderTimeStamp); + + if (folderSize == fileSize && numUnreadMessages >= 0) + { + if (!folderSize) + { + *aResult = true; + return NS_OK; + } + if (!gGotGlobalPrefs) + { + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + { + rv = pPrefBranch->GetIntPref("mail.db_timestamp_leeway", &gTimeStampLeeway); + gGotGlobalPrefs = true; + } + } + // if those values are ok, check time stamp + if (gTimeStampLeeway == 0) + *aResult = folderDate == actualFolderTimeStamp; + else + *aResult = std::abs((int32_t) (actualFolderTimeStamp - folderDate)) <= gTimeStampLeeway; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::SetSummaryFileValid(nsIMsgFolder *aFolder, + nsIMsgDatabase *aDB, + bool aValid) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aDB); + // We only need to do this for local folders. + nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(aFolder)); + if (!localFolder) + return NS_OK; + + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = aDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + NS_ENSURE_SUCCESS(rv, rv); + bool exists; + pathFile->Exists(&exists); + if (!exists) + return NS_MSG_ERROR_FOLDER_MISSING; + + if (aValid) + { + uint32_t actualFolderTimeStamp; + int64_t fileSize; + GetMailboxModProperties(aFolder, &fileSize, &actualFolderTimeStamp); + folderInfo->SetFolderSize(fileSize); + folderInfo->SetFolderDate(actualFolderTimeStamp); + } + else + { + folderInfo->SetVersion(0); // that ought to do the trick. + } + aDB->Commit(nsMsgDBCommitType::kLargeCommit); + return rv; +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::DeleteFolder(nsIMsgFolder *aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + //Delete mailbox + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + pathFile->Remove(false); + + bool isDirectory = false; + pathFile->IsDirectory(&isDirectory); + if (!isDirectory) + { + nsAutoString leafName; + pathFile->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + pathFile->SetLeafName(leafName); + } + isDirectory = false; + pathFile->IsDirectory(&isDirectory); + //If this is a directory, then remove it. + return isDirectory ? pathFile->Remove(true) : NS_OK; +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::RenameFolder(nsIMsgFolder *aFolder, + const nsAString & aNewName, + nsIMsgFolder **aNewFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aNewFolder); + + uint32_t numChildren; + aFolder->GetNumSubFolders(&numChildren); + nsString existingName; + aFolder->GetName(existingName); + + nsCOMPtr<nsIFile> oldPathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgFolder> parentFolder; + rv = aFolder->GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder); + + nsCOMPtr<nsIFile> oldSummaryFile; + rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> dirFile; + oldPathFile->Clone(getter_AddRefs(dirFile)); + + if (numChildren > 0) + { + rv = CreateDirectoryForFolder(dirFile); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsAutoString safeName(aNewName); + NS_MsgHashIfNecessary(safeName); + + nsAutoCString oldLeafName; + oldPathFile->GetNativeLeafName(oldLeafName); + + nsCOMPtr<nsIFile> parentPathFile; + parentFolder->GetFilePath(getter_AddRefs(parentPathFile)); + NS_ENSURE_SUCCESS(rv,rv); + + bool isDirectory = false; + parentPathFile->IsDirectory(&isDirectory); + if (!isDirectory) + { + nsAutoString leafName; + parentPathFile->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + rv = parentPathFile->SetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + } + + aFolder->ForceDBClosed(); + //save off dir name before appending .msf + rv = oldPathFile->MoveTo(nullptr, safeName); + if (NS_FAILED(rv)) + return rv; + + nsString dbName(safeName); + dbName += NS_LITERAL_STRING(SUMMARY_SUFFIX); + oldSummaryFile->MoveTo(nullptr, dbName); + + if (numChildren > 0) + { + // rename "*.sbd" directory + nsAutoString newNameDirStr(safeName); + newNameDirStr += NS_LITERAL_STRING(".sbd"); + dirFile->MoveTo(nullptr, newNameDirStr); + } + + return parentFolder->AddSubfolder(safeName, aNewFolder); +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::CopyFolder(nsIMsgFolder *aSrcFolder, + nsIMsgFolder *aDstFolder, + bool aIsMoveFolder, + nsIMsgWindow *aMsgWindow, + nsIMsgCopyServiceListener *aListener, + const nsAString &aNewName) +{ + NS_ENSURE_ARG_POINTER(aSrcFolder); + NS_ENSURE_ARG_POINTER(aDstFolder); + + nsAutoString folderName; + if (aNewName.IsEmpty()) + aSrcFolder->GetName(folderName); + else + folderName.Assign(aNewName); + + nsAutoString safeFolderName(folderName); + NS_MsgHashIfNecessary(safeFolderName); + nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder(do_QueryInterface(aSrcFolder)); + nsCOMPtr<nsIMsgDatabase> srcDB; + if (localSrcFolder) + localSrcFolder->GetDatabaseWOReparse(getter_AddRefs(srcDB)); + bool summaryValid = !!srcDB; + srcDB = nullptr; + aSrcFolder->ForceDBClosed(); + + nsCOMPtr<nsIFile> oldPath; + nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIFile> summaryFile; + GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile)); + + nsCOMPtr<nsIFile> newPath; + rv = aDstFolder->GetFilePath(getter_AddRefs(newPath)); + NS_ENSURE_SUCCESS(rv,rv); + + bool newPathIsDirectory = false; + newPath->IsDirectory(&newPathIsDirectory); + if (!newPathIsDirectory) + { + AddDirectorySeparator(newPath); + rv = newPath->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) + rv = NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIFile> origPath; + oldPath->Clone(getter_AddRefs(origPath)); + + //copying necessary for aborting.... if failure return + rv = oldPath->CopyTo(newPath, safeFolderName); + NS_ENSURE_SUCCESS(rv, rv); // Will fail if a file by that name exists + + // Copy to dir can fail if filespec does not exist. If copy fails, we test + // if the filespec exist or not, if it does not that's ok, we continue + // without copying it. If it fails and filespec exist and is not zero sized + // there is real problem + // Copy the file to the new dir + nsAutoString dbName(safeFolderName); + dbName += NS_LITERAL_STRING(SUMMARY_SUFFIX); + rv = summaryFile->CopyTo(newPath, dbName); + if (NS_FAILED(rv)) // Test if the copy is successful + { + // Test if the filespec has data + bool exists; + int64_t fileSize; + summaryFile->Exists(&exists); + summaryFile->GetFileSize(&fileSize); + if (exists && fileSize > 0) + NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked ! + // else case is filespec is zero sized, no need to copy it, + // not an error + } + + nsCOMPtr<nsIMsgFolder> newMsgFolder; + rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + // linux and mac are not good about maintaining the file stamp when copying + // folders around. So if the source folder db is good, set the dest db as + // good too. + nsCOMPtr<nsIMsgDatabase> destDB; + if (summaryValid) + { + nsAutoString folderLeafName; + origPath->GetLeafName(folderLeafName); + newPath->Append(folderLeafName); + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgDBService->OpenMailDBFromFile(newPath, newMsgFolder, false, + true, getter_AddRefs(destDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE && destDB) + destDB->SetSummaryValid(true); + } + newMsgFolder->SetPrettyName(folderName); + uint32_t flags; + aSrcFolder->GetFlags(&flags); + newMsgFolder->SetFlags(flags); + bool changed = false; + rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed); + if (changed) + aSrcFolder->AlertFilterChanged(aMsgWindow); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = aSrcFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy subfolders to the new location + nsresult copyStatus = NS_OK; + nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(do_QueryInterface(newMsgFolder, &rv)); + if (NS_SUCCEEDED(rv)) + { + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore && + NS_SUCCEEDED(copyStatus)) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item)); + if (!folder) + continue; + + copyStatus = localNewFolder->CopyFolderLocal(folder, false, + aMsgWindow, aListener); + // Test if the call succeeded, if not we have to stop recursive call + if (NS_FAILED(copyStatus)) + { + // Copy failed we have to notify caller to handle the error and stop + // moving the folders. In case this happens to the topmost level of + // recursive call, then we just need to break from the while loop and + // go to error handling code. + if (!aIsMoveFolder) + return copyStatus; + break; + } + } + } + + if (aIsMoveFolder && NS_SUCCEEDED(copyStatus)) + { + if (localNewFolder) + { + nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder)); + localNewFolder->OnCopyCompleted(srcSupport, true); + } + + // Notify the "folder" that was dragged and dropped has been created. No + // need to do this for its subfolders. isMoveFolder will be true for folder. + aDstFolder->NotifyItemAdded(newMsgFolder); + + nsCOMPtr<nsIMsgFolder> msgParent; + aSrcFolder->GetParent(getter_AddRefs(msgParent)); + aSrcFolder->SetParent(nullptr); + if (msgParent) + { + // The files have already been moved, so delete storage false + msgParent->PropagateDelete(aSrcFolder, false, aMsgWindow); + oldPath->Remove(false); //berkeley mailbox + // We need to force closed the source db + nsCOMPtr<nsIMsgDatabase> srcDB; + aSrcFolder->Delete(); + + nsCOMPtr<nsIFile> parentPath; + rv = msgParent->GetFilePath(getter_AddRefs(parentPath)); + NS_ENSURE_SUCCESS(rv,rv); + + AddDirectorySeparator(parentPath); + nsCOMPtr<nsISimpleEnumerator> children; + parentPath->GetDirectoryEntries(getter_AddRefs(children)); + bool more; + // checks if the directory is empty or not + if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more) + parentPath->Remove(true); + } + } + else + { + // This is the case where the copy of a subfolder failed. + // We have to delete the newDirectory tree to make a "rollback". + // Someone should add a popup to warn the user that the move was not + // possible. + if (aIsMoveFolder && NS_FAILED(copyStatus)) + { + nsCOMPtr<nsIMsgFolder> msgParent; + newMsgFolder->ForceDBClosed(); + newMsgFolder->GetParent(getter_AddRefs(msgParent)); + newMsgFolder->SetParent(nullptr); + if (msgParent) + { + msgParent->PropagateDelete(newMsgFolder, false, aMsgWindow); + newMsgFolder->Delete(); + newMsgFolder->ForceDBClosed(); + AddDirectorySeparator(newPath); + newPath->Remove(true); //berkeley mailbox + } + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::GetNewMsgOutputStream(nsIMsgFolder *aFolder, + nsIMsgDBHdr **aNewMsgHdr, + bool *aReusable, + nsIOutputStream **aResult) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aNewMsgHdr); + NS_ENSURE_ARG_POINTER(aReusable); + NS_ENSURE_ARG_POINTER(aResult); + +#ifdef _DEBUG + NS_ASSERTION(m_streamOutstandingFolder != aFolder, "didn't finish prev msg"); + m_streamOutstandingFolder = aFolder; +#endif + *aReusable = true; + nsCOMPtr<nsIFile> mboxFile; + aFolder->GetFilePath(getter_AddRefs(mboxFile)); + nsCOMPtr<nsIMsgDatabase> db; + aFolder->GetMsgDatabase(getter_AddRefs(db)); + if (!db && !*aNewMsgHdr) + NS_WARNING("no db, and no message header"); + bool exists; + nsresult rv; + mboxFile->Exists(&exists); + if (!exists) { + rv = mboxFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCString URI; + aFolder->GetURI(URI); + nsCOMPtr<nsISeekableStream> seekable; + if (m_outputStreams.Get(URI, aResult)) + { + seekable = do_QueryInterface(*aResult, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0); + if (NS_FAILED(rv)) + { + m_outputStreams.Remove(URI); + NS_RELEASE(*aResult); + } + } + if (!*aResult) + { + rv = MsgGetFileStream(mboxFile, aResult); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed opening offline store for output"); + if (NS_FAILED(rv)) + printf("failed opening offline store for %s\n", URI.get()); + NS_ENSURE_SUCCESS(rv, rv); + seekable = do_QueryInterface(*aResult, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0); + NS_ENSURE_SUCCESS(rv, rv); + m_outputStreams.Put(URI, *aResult); + } + int64_t filePos; + seekable->Tell(&filePos); + + if (db && !*aNewMsgHdr) + { + db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr); + } + + if (*aNewMsgHdr) + { + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", filePos); + (*aNewMsgHdr)->SetMessageOffset(filePos); + (*aNewMsgHdr)->SetStringProperty("storeToken", storeToken); + } + return rv; +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::DiscardNewMessage(nsIOutputStream *aOutputStream, + nsIMsgDBHdr *aNewHdr) +{ + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_ENSURE_ARG_POINTER(aNewHdr); +#ifdef _DEBUG + m_streamOutstandingFolder = nullptr; +#endif + uint64_t hdrOffset; + aNewHdr->GetMessageOffset(&hdrOffset); + aOutputStream->Close(); + nsCOMPtr<nsIFile> mboxFile; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + folder->GetFilePath(getter_AddRefs(mboxFile)); + return mboxFile->SetFileSize(hdrOffset); +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::FinishNewMessage(nsIOutputStream *aOutputStream, + nsIMsgDBHdr *aNewHdr) +{ +#ifdef _DEBUG + m_streamOutstandingFolder = nullptr; +#endif + NS_ENSURE_ARG_POINTER(aOutputStream); +// NS_ENSURE_ARG_POINTER(aNewHdr); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr *aNewHdr, + nsIMsgFolder *aDestFolder, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aNewHdr); + NS_ENSURE_ARG_POINTER(aDestFolder); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::GetMsgInputStream(nsIMsgFolder *aMsgFolder, + const nsACString &aMsgToken, + int64_t *aOffset, + nsIMsgDBHdr *aMsgHdr, + bool *aReusable, + nsIInputStream **aResult) +{ + NS_ENSURE_ARG_POINTER(aMsgFolder); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aOffset); + + // If there is no store token, then we set it to the existing message offset. + if (aMsgToken.IsEmpty()) + { + uint64_t offset; + NS_ENSURE_ARG_POINTER(aMsgHdr); + aMsgHdr->GetMessageOffset(&offset); + *aOffset = int64_t(offset); + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", *aOffset); + aMsgHdr->SetStringProperty("storeToken", storeToken); + } + else + *aOffset = ParseUint64Str(PromiseFlatCString(aMsgToken).get()); + *aReusable = true; + nsCString URI; + nsCOMPtr<nsIFile> mboxFile; + + aMsgFolder->GetURI(URI); + aMsgFolder->GetFilePath(getter_AddRefs(mboxFile)); + return NS_NewLocalFileInputStream(aResult, mboxFile); +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::DeleteMessages(nsIArray *aHdrArray) +{ + return ChangeFlags(aHdrArray, nsMsgMessageFlags::Expunged, true); +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::CopyMessages(bool isMove, nsIArray *aHdrArray, + nsIMsgFolder *aDstFolder, + nsIMsgCopyServiceListener *aListener, + nsIArray **aDstHdrs, + nsITransaction **aUndoAction, + bool *aCopyDone) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + NS_ENSURE_ARG_POINTER(aDstFolder); + NS_ENSURE_ARG_POINTER(aDstHdrs); + NS_ENSURE_ARG_POINTER(aCopyDone); + *aDstHdrs = nullptr; + *aUndoAction = nullptr; + *aCopyDone = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::GetSupportsCompaction(bool *aSupportsCompaction) +{ + NS_ENSURE_ARG_POINTER(aSupportsCompaction); + *aSupportsCompaction = true; + return NS_OK; +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::CompactFolder(nsIMsgFolder *aFolder, + nsIUrlListener *aListener, + nsIMsgWindow *aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(aFolder); + nsresult rv; + nsCOMPtr<nsIMsgFolderCompactor> folderCompactor = + do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t expungedBytes = 0; + aFolder->GetExpungedBytes(&expungedBytes); + // check if we need to compact the folder + return (expungedBytes > 0) ? + folderCompactor->Compact(aFolder, false, aListener, aMsgWindow) : + aFolder->NotifyCompactCompleted(); +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::RebuildIndex(nsIMsgFolder *aFolder, + nsIMsgDatabase *aMsgDB, + nsIMsgWindow *aMsgWindow, + nsIUrlListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aFolder); + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + if (NS_FAILED(rv)) + return rv; + + bool isLocked; + aFolder->GetLocked(&isLocked); + if (isLocked) + { + NS_ASSERTION(false, "Could not get folder lock"); + return NS_MSG_FOLDER_BUSY; + } + + nsCOMPtr<nsIMailboxService> mailboxService = + do_GetService(NS_MAILBOXSERVICE_CONTRACTID1, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsMsgMailboxParser> parser = new nsMsgMailboxParser(aFolder); + NS_ENSURE_TRUE(parser, NS_ERROR_OUT_OF_MEMORY); + rv = parser->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mailboxService->ParseMailbox(aMsgWindow, pathFile, parser, aListener, + nullptr); + if (NS_SUCCEEDED(rv)) + ResetForceReparse(aMsgDB); + return rv; +} + +nsresult +nsMsgBrkMBoxStore::GetOutputStream(nsIArray *aHdrArray, + nsCOMPtr<nsIOutputStream> &outputStream, + nsCOMPtr<nsISeekableStream> &seekableStream, + int64_t &restorePos) +{ + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, 0, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgFolder> folder; + msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString URI; + folder->GetURI(URI); + restorePos = -1; + if (m_outputStreams.Get(URI, getter_AddRefs(outputStream))) + { + seekableStream = do_QueryInterface(outputStream); + rv = seekableStream->Tell(&restorePos); + if (NS_FAILED(rv)) + { + outputStream = nullptr; + m_outputStreams.Remove(URI); + } + } + nsCOMPtr<nsIFile> mboxFile; + folder->GetFilePath(getter_AddRefs(mboxFile)); + if (!outputStream) + { + rv = MsgGetFileStream(mboxFile, getter_AddRefs(outputStream)); + seekableStream = do_QueryInterface(outputStream); + if (NS_SUCCEEDED(rv)) + m_outputStreams.Put(URI, outputStream); + } + return rv; +} + +void nsMsgBrkMBoxStore::SetDBValid(nsIMsgDBHdr *aHdr) +{ + nsCOMPtr<nsIMsgFolder> folder; + aHdr->GetFolder(getter_AddRefs(folder)); + if (folder) + { + nsCOMPtr<nsIMsgDatabase> db; + folder->GetMsgDatabase(getter_AddRefs(db)); + if (db) + SetSummaryFileValid(folder, db, true); + } +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeFlags(nsIArray *aHdrArray, + uint32_t aFlags, + bool aSet) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + nsCOMPtr<nsIOutputStream> outputStream; + nsCOMPtr<nsISeekableStream> seekableStream; + int64_t restoreStreamPos; + + uint32_t messageCount; + nsresult rv = aHdrArray->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + if (!messageCount) + return NS_ERROR_INVALID_ARG; + + rv = GetOutputStream(aHdrArray, outputStream, seekableStream, + restoreStreamPos); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + for (uint32_t i = 0; i < messageCount; i++) + { + msgHdr = do_QueryElementAt(aHdrArray, i, &rv); + // Seek to x-mozilla-status offset and rewrite value. + rv = UpdateFolderFlag(msgHdr, aSet, aFlags, outputStream); + if (NS_FAILED(rv)) + { + NS_WARNING("updateFolderFlag failed"); + break; + } + } + if (restoreStreamPos != -1) + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, restoreStreamPos); + else if (outputStream) + outputStream->Close(); + if (messageCount > 0) + { + msgHdr = do_QueryElementAt(aHdrArray, 0); + SetDBValid(msgHdr); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeKeywords(nsIArray *aHdrArray, + const nsACString &aKeywords, + bool aAdd) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + nsCOMPtr<nsIOutputStream> outputStream; + nsCOMPtr<nsISeekableStream> seekableStream; + int64_t restoreStreamPos; + + uint32_t messageCount; + nsresult rv = aHdrArray->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + if (!messageCount) + return NS_ERROR_INVALID_ARG; + + rv = GetOutputStream(aHdrArray, outputStream, seekableStream, + restoreStreamPos); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(outputStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>); + NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); + + // For each message, we seek to the beginning of the x-mozilla-status header, + // and start reading lines, looking for x-mozilla-keys: headers; If we're + // adding the keyword and we find + // a header with the desired keyword already in it, we don't need to + // do anything. Likewise, if removing keyword and we don't find it, + // we don't need to do anything. Otherwise, if adding, we need to + // see if there's an x-mozilla-keys + // header with room for the new keyword. If so, we replace the + // corresponding number of spaces with the keyword. If no room, + // we can't do anything until the folder is compacted and another + // x-mozilla-keys header is added. In that case, we set a property + // on the header, which the compaction code will check. + + nsTArray<nsCString> keywordArray; + ParseString(aKeywords, ' ', keywordArray); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + for (uint32_t i = 0; i < messageCount; ++i) // for each message + { + msgHdr = do_QueryElementAt(aHdrArray, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + uint64_t messageOffset; + msgHdr->GetMessageOffset(&messageOffset); + uint32_t statusOffset = 0; + (void)msgHdr->GetStatusOffset(&statusOffset); + uint64_t desiredOffset = messageOffset + statusOffset; + + ChangeKeywordsHelper(msgHdr, desiredOffset, lineBuffer, keywordArray, + aAdd, outputStream, seekableStream, inputStream); + } + lineBuffer = nullptr; + if (restoreStreamPos != -1) + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, restoreStreamPos); + else if (outputStream) + outputStream->Close(); + if (messageCount > 0) + { + msgHdr = do_QueryElementAt(aHdrArray, 0); + SetDBValid(msgHdr); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::GetStoreType(nsACString& aType) +{ + aType.AssignLiteral("mbox"); + return NS_OK; +} + +// Iterates over the files in the "path" directory, and adds subfolders to +// parent for each mailbox file found. +nsresult +nsMsgBrkMBoxStore::AddSubFolders(nsIMsgFolder *parent, nsCOMPtr<nsIFile> &path, + bool deep) +{ + nsresult rv; + nsCOMPtr<nsIFile> tmp; // at top level so we can safely assign to path + bool isDirectory; + path->IsDirectory(&isDirectory); + if (!isDirectory) + { + rv = path->Clone(getter_AddRefs(tmp)); + path = tmp; + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString leafName; + path->GetLeafName(leafName); + leafName.AppendLiteral(".sbd"); + path->SetLeafName(leafName); + path->IsDirectory(&isDirectory); + } + if (!isDirectory) + return NS_OK; + // first find out all the current subfolders and files, before using them + // while creating new subfolders; we don't want to modify and iterate the same + // directory at once. + nsCOMArray<nsIFile> currentDirEntries; + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr<nsISupports> aSupport; + directoryEnumerator->GetNext(getter_AddRefs(aSupport)); + nsCOMPtr<nsIFile> currentFile(do_QueryInterface(aSupport, &rv)); + if (currentFile) + currentDirEntries.AppendObject(currentFile); + } + + // add the folders + int32_t count = currentDirEntries.Count(); + for (int32_t i = 0; i < count; ++i) + { + nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]); + + nsAutoString leafName; + currentFile->GetLeafName(leafName); + directoryEnumerator->HasMoreElements(&hasMore); + // here we should handle the case where the current file is a .sbd directory + // w/o a matching folder file, or a directory w/o the name .sbd + if (nsShouldIgnoreFile(leafName)) + continue; + + nsCOMPtr<nsIMsgFolder> child; + rv = parent->AddSubfolder(leafName, getter_AddRefs(child)); + if (child) + { + nsString folderName; + child->GetName(folderName); // try to get it from cache/db + if (folderName.IsEmpty()) + child->SetPrettyName(leafName); + if (deep) + { + nsCOMPtr<nsIFile> path; + rv = child->GetFilePath(getter_AddRefs(path)); + AddSubFolders(child, path, true); + } + } + } + return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv; +} + +/* Finds the directory associated with this folder. That is if the path is + c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't + currently exist then it will create it. Path is strictly an out parameter. + */ +nsresult nsMsgBrkMBoxStore::CreateDirectoryForFolder(nsIFile *path) +{ + nsresult rv = NS_OK; + + bool pathIsDirectory = false; + path->IsDirectory(&pathIsDirectory); + if (!pathIsDirectory) + { + // If the current path isn't a directory, add directory separator + // and test it out. + nsAutoString leafName; + path->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + rv = path->SetLeafName(leafName); + if (NS_FAILED(rv)) + return rv; + + //If that doesn't exist, then we have to create this directory + pathIsDirectory = false; + path->IsDirectory(&pathIsDirectory); + if (!pathIsDirectory) + { + bool pathExists; + path->Exists(&pathExists); + //If for some reason there's a file with the directory separator + //then we are going to fail. + rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY : + path->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + } + return rv; +} + diff --git a/mailnews/local/src/nsMsgBrkMBoxStore.h b/mailnews/local/src/nsMsgBrkMBoxStore.h new file mode 100644 index 000000000..4d10e672d --- /dev/null +++ b/mailnews/local/src/nsMsgBrkMBoxStore.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +/** + Class for handling Berkeley Mailbox stores. +*/ + +#ifndef nsMsgBrkMboxStore_h__ +#define nsMsgBrkMboxStore_h__ + +#include "nsMsgLocalStoreUtils.h" +#include "nsIFile.h" +#include "nsInterfaceHashtable.h" +#include "nsISeekableStream.h" + +class nsMsgBrkMBoxStore final : public nsMsgLocalStoreUtils, nsIMsgPluggableStore +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPLUGGABLESTORE + + nsMsgBrkMBoxStore(); + +private: + ~nsMsgBrkMBoxStore(); + +protected: + nsresult AddSubFolders(nsIMsgFolder *parent, nsCOMPtr<nsIFile> &path, bool deep); + nsresult CreateDirectoryForFolder(nsIFile *path); + nsresult GetOutputStream(nsIArray *aHdrArray, + nsCOMPtr<nsIOutputStream> &outputStream, + nsCOMPtr<nsISeekableStream> &seekableStream, + int64_t &restorePos); + void GetMailboxModProperties(nsIMsgFolder *aFolder, + int64_t *aSize, uint32_t *aDate); + void SetDBValid(nsIMsgDBHdr *aHdr); + // We don't want to keep re-opening an output stream when downloading + // multiple pop3 messages, or adjusting x-mozilla-status headers, so + // we cache output streams based on folder uri's. If the caller has closed + // the stream, we'll get a new one. + nsInterfaceHashtable<nsCStringHashKey, nsIOutputStream> m_outputStreams; + +#ifdef _DEBUG + nsCOMPtr<nsIMsgFolder> m_streamOutstandingFolder; +#endif +}; + +#endif diff --git a/mailnews/local/src/nsMsgLocalStoreUtils.cpp b/mailnews/local/src/nsMsgLocalStoreUtils.cpp new file mode 100644 index 000000000..17d9b0f29 --- /dev/null +++ b/mailnews/local/src/nsMsgLocalStoreUtils.cpp @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... +#include "nsMsgLocalStoreUtils.h" +#include "nsIFile.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgDatabase.h" +#include "prprf.h" + +#define EXTRA_SAFETY_SPACE 0x400000 // (4MiB) + +nsMsgLocalStoreUtils::nsMsgLocalStoreUtils() +{ +} + +nsresult +nsMsgLocalStoreUtils::AddDirectorySeparator(nsIFile *path) +{ + nsAutoString leafName; + path->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + return path->SetLeafName(leafName); +} + +bool +nsMsgLocalStoreUtils::nsShouldIgnoreFile(nsAString& name) +{ + char16_t firstChar = name.First(); + if (firstChar == '.' || firstChar == '#' || + name.CharAt(name.Length() - 1) == '~') + return true; + + if (name.LowerCaseEqualsLiteral("msgfilterrules.dat") || + name.LowerCaseEqualsLiteral("rules.dat") || + name.LowerCaseEqualsLiteral("filterlog.html") || + name.LowerCaseEqualsLiteral("junklog.html") || + name.LowerCaseEqualsLiteral("rulesbackup.dat")) + return true; + + // don't add summary files to the list of folders; + // don't add popstate files to the list either, or rules (sort.dat). + if (StringEndsWith(name, NS_LITERAL_STRING(".snm")) || + name.LowerCaseEqualsLiteral("popstate.dat") || + name.LowerCaseEqualsLiteral("sort.dat") || + name.LowerCaseEqualsLiteral("mailfilt.log") || + name.LowerCaseEqualsLiteral("filters.js") || + StringEndsWith(name, NS_LITERAL_STRING(".toc"))) + return true; + + // ignore RSS data source files + if (name.LowerCaseEqualsLiteral("feeds.rdf") || + name.LowerCaseEqualsLiteral("feeditems.rdf") || + StringBeginsWith(name, NS_LITERAL_STRING("feeditems_error"))) + return true; + + // The .mozmsgs dir is for spotlight support + return (StringEndsWith(name, NS_LITERAL_STRING(".mozmsgs")) || + StringEndsWith(name, NS_LITERAL_STRING(".sbd")) || + StringEndsWith(name, NS_LITERAL_STRING(SUMMARY_SUFFIX))); +} + +/** + * We're passed a stream positioned at the start of the message. + * We start reading lines, looking for x-mozilla-keys: headers; If we're + * adding the keyword and we find a header with the desired keyword already + * in it, we don't need to do anything. Likewise, if removing keyword and we + * don't find it,we don't need to do anything. Otherwise, if adding, we need + * to see if there's an x-mozilla-keys header with room for the new keyword. + * If so, we replace the corresponding number of spaces with the keyword. + * If no room, we can't do anything until the folder is compacted and another + * x-mozilla-keys header is added. In that case, we set a property + * on the header, which the compaction code will check. + * This is not true for maildir, however, since it won't require compaction. + */ + +void +nsMsgLocalStoreUtils::ChangeKeywordsHelper(nsIMsgDBHdr *message, + uint64_t desiredOffset, + nsLineBuffer<char> *lineBuffer, + nsTArray<nsCString> &keywordArray, + bool aAdd, + nsIOutputStream *outputStream, + nsISeekableStream *seekableStream, + nsIInputStream *inputStream) +{ + uint32_t bytesWritten; + + for (uint32_t i = 0; i < keywordArray.Length(); i++) + { + nsAutoCString header; + nsAutoCString keywords; + bool done = false; + uint32_t len = 0; + nsAutoCString keywordToWrite(" "); + + keywordToWrite.Append(keywordArray[i]); + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, desiredOffset); + // need to reset lineBuffer, which is cheaper than creating a new one. + lineBuffer->start = lineBuffer->end = lineBuffer->buf; + bool inKeywordHeader = false; + bool foundKeyword = false; + int64_t offsetToAddKeyword = 0; + bool more; + message->GetMessageSize(&len); + // loop through + while (!done) + { + int64_t lineStartPos; + seekableStream->Tell(&lineStartPos); + // we need to adjust the linestart pos by how much extra the line + // buffer has read from the stream. + lineStartPos -= (lineBuffer->end - lineBuffer->start); + // NS_ReadLine doesn't return line termination chars. + nsCString keywordHeaders; + nsresult rv = NS_ReadLine(inputStream, lineBuffer, keywordHeaders, &more); + if (NS_SUCCEEDED(rv)) + { + if (keywordHeaders.IsEmpty()) + break; // passed headers; no x-mozilla-keywords header; give up. + if (StringBeginsWith(keywordHeaders, + NS_LITERAL_CSTRING(HEADER_X_MOZILLA_KEYWORDS))) + inKeywordHeader = true; + else if (inKeywordHeader && (keywordHeaders.CharAt(0) == ' ' || + keywordHeaders.CharAt(0) == '\t')) + ; // continuation header line + else if (inKeywordHeader) + break; + else + continue; + uint32_t keywordHdrLength = keywordHeaders.Length(); + int32_t startOffset, keywordLength; + // check if we have the keyword + if (MsgFindKeyword(keywordArray[i], keywordHeaders, &startOffset, + &keywordLength)) + { + foundKeyword = true; + if (!aAdd) // if we're removing, remove it, and break; + { + keywordHeaders.Cut(startOffset, keywordLength); + for (int32_t j = keywordLength; j > 0; j--) + keywordHeaders.Append(' '); + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, lineStartPos); + outputStream->Write(keywordHeaders.get(), keywordHeaders.Length(), + &bytesWritten); + } + offsetToAddKeyword = 0; + // if adding and we already have the keyword, done + done = true; + break; + } + // argh, we need to check all the lines to see if we already have the + // keyword, but if we don't find it, we want to remember the line and + // position where we have room to add the keyword. + if (aAdd) + { + nsAutoCString curKeywordHdr(keywordHeaders); + // strip off line ending spaces. + curKeywordHdr.Trim(" ", false, true); + if (!offsetToAddKeyword && curKeywordHdr.Length() + + keywordToWrite.Length() < keywordHdrLength) + offsetToAddKeyword = lineStartPos + curKeywordHdr.Length(); + } + } + } + if (aAdd && !foundKeyword) + { + if (!offsetToAddKeyword) + message->SetUint32Property("growKeywords", 1); + else + { + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, + offsetToAddKeyword); + outputStream->Write(keywordToWrite.get(), keywordToWrite.Length(), + &bytesWritten); + } + } + } +} + +nsresult +nsMsgLocalStoreUtils::UpdateFolderFlag(nsIMsgDBHdr *mailHdr, bool bSet, + nsMsgMessageFlagType flag, + nsIOutputStream *fileStream) +{ + uint32_t statusOffset; + uint64_t msgOffset; + nsresult rv = mailHdr->GetStatusOffset(&statusOffset); + // This probably means there's no x-mozilla-status header, so + // we just ignore this. + if (NS_FAILED(rv) || (statusOffset == 0)) + return NS_OK; + rv = mailHdr->GetMessageOffset(&msgOffset); + NS_ENSURE_SUCCESS(rv, rv); + uint64_t statusPos = msgOffset + statusOffset; + nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(fileStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos); + NS_ENSURE_SUCCESS(rv, rv); + char buf[50]; + buf[0] = '\0'; + nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(fileStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t bytesRead; + if (NS_SUCCEEDED(inputStream->Read(buf, X_MOZILLA_STATUS_LEN + 6, + &bytesRead))) + { + buf[bytesRead] = '\0'; + if (strncmp(buf, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) == 0 && + strncmp(buf + X_MOZILLA_STATUS_LEN, ": ", 2) == 0 && + strlen(buf) >= X_MOZILLA_STATUS_LEN + 6) + { + uint32_t flags; + uint32_t bytesWritten; + (void)mailHdr->GetFlags(&flags); + if (!(flags & nsMsgMessageFlags::Expunged)) + { + char *p = buf + X_MOZILLA_STATUS_LEN + 2; + + nsresult errorCode = NS_OK; + flags = nsDependentCString(p).ToInteger(&errorCode, 16); + + uint32_t curFlags; + (void)mailHdr->GetFlags(&curFlags); + flags = (flags & nsMsgMessageFlags::Queued) | + (curFlags & ~nsMsgMessageFlags::RuntimeOnly); + if (bSet) + flags |= flag; + else + flags &= ~flag; + } + else + { + flags &= ~nsMsgMessageFlags::RuntimeOnly; + } + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos); + // We are filing out x-mozilla-status flags here + PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS_FORMAT, + flags & 0x0000FFFF); + int32_t lineLen = PL_strlen(buf); + uint64_t status2Pos = statusPos + lineLen; + fileStream->Write(buf, lineLen, &bytesWritten); + + if (flag & 0xFFFF0000) + { + // Time to update x-mozilla-status2, + // first find it by finding end of previous line, see bug 234935. + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos); + do + { + rv = inputStream->Read(buf, 1, &bytesRead); + status2Pos++; + } while (NS_SUCCEEDED(rv) && (*buf == '\n' || *buf == '\r')); + status2Pos--; + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos); + if (NS_SUCCEEDED(inputStream->Read(buf, X_MOZILLA_STATUS2_LEN + 10, + &bytesRead))) + { + if (strncmp(buf, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN) == 0 && + strncmp(buf + X_MOZILLA_STATUS2_LEN, ": ", 2) == 0 && + strlen(buf) >= X_MOZILLA_STATUS2_LEN + 10) + { + uint32_t dbFlags; + (void)mailHdr->GetFlags(&dbFlags); + dbFlags &= 0xFFFF0000; + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos); + PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS2_FORMAT, dbFlags); + fileStream->Write(buf, PL_strlen(buf), &bytesWritten); + } + } + } + } + else + { +#ifdef DEBUG + printf("Didn't find %s where expected at position %ld\n" + "instead, found %s.\n", + X_MOZILLA_STATUS, (long) statusPos, buf); +#endif + rv = NS_ERROR_FAILURE; + } + } + else + rv = NS_ERROR_FAILURE; + return rv; +} + +/** + * Returns true if there is enough space on disk. + * + * @param aFile Any file in the message store that is on a logical + * disk volume so that it can be queried for disk space. + * @param aSpaceRequested The size of free space there must be on the disk + * to return true. + */ +bool +nsMsgLocalStoreUtils::DiskSpaceAvailableInStore(nsIFile *aFile, uint64_t aSpaceRequested) +{ + int64_t diskFree; + nsresult rv = aFile->GetDiskSpaceAvailable(&diskFree); + if (NS_SUCCEEDED(rv)) { +#ifdef DEBUG + printf("GetDiskSpaceAvailable returned: %lld bytes\n", (long long)diskFree); +#endif + // When checking for disk space available, take into consideration + // possible database changes, therefore ask for a little more + // (EXTRA_SAFETY_SPACE) than what the requested size is. Also, due to disk + // sector sizes, allocation blocks, etc. The space "available" may be greater + // than the actual space usable. + return ((aSpaceRequested + EXTRA_SAFETY_SPACE) < (uint64_t) diskFree); + } else if (rv == NS_ERROR_NOT_IMPLEMENTED) { + // The call to GetDiskSpaceAvailable is not implemented! + // This will happen on certain platforms where GetDiskSpaceAvailable + // is not implemented. Since people on those platforms still need + // to download mail, we will simply bypass the disk-space check. + // + // We'll leave a debug message to warn people. +#ifdef DEBUG + printf("Call to GetDiskSpaceAvailable FAILED because it is not implemented!\n"); +#endif + return true; + } else { + printf("Call to GetDiskSpaceAvailable FAILED!\n"); + return false; + } +} + +/** + * Resets forceReparse in the database. + * + * @param aMsgDb The database to reset. + */ +void +nsMsgLocalStoreUtils::ResetForceReparse(nsIMsgDatabase *aMsgDB) +{ + if (aMsgDB) + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + aMsgDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (folderInfo) + folderInfo->SetBooleanProperty("forceReparse", false); + } +} diff --git a/mailnews/local/src/nsMsgLocalStoreUtils.h b/mailnews/local/src/nsMsgLocalStoreUtils.h new file mode 100644 index 000000000..182e0d15a --- /dev/null +++ b/mailnews/local/src/nsMsgLocalStoreUtils.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ +#ifndef nsMsgLocalStoreUtils_h__ +#define nsMsgLocalStoreUtils_h__ + +#include "msgCore.h" +#include "nsIMsgPluggableStore.h" +#include "nsStringGlue.h" +#include "nsReadLine.h" +#include "nsISeekableStream.h" +#include "nsIMsgHdr.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMailHeaders.h" +#include "nsMsgUtils.h" +#include "nsIOutputStream.h" +#include "nsMsgMessageFlags.h" + +/** + * Utility Class for handling local mail stores. Berkeley Mailbox + * and MailDir stores inherit from this class to share some code. +*/ + +class nsMsgLocalStoreUtils +{ +public: + nsMsgLocalStoreUtils(); + + static nsresult AddDirectorySeparator(nsIFile *path); + static bool nsShouldIgnoreFile(nsAString& name); + static void ChangeKeywordsHelper(nsIMsgDBHdr *message, + uint64_t desiredOffset, + nsLineBuffer<char> *lineBuffer, + nsTArray<nsCString> &keywordArray, + bool aAdd, + nsIOutputStream *outputStream, + nsISeekableStream *seekableStream, + nsIInputStream *inputStream); + static void ResetForceReparse(nsIMsgDatabase *aMsgDB); + + nsresult UpdateFolderFlag(nsIMsgDBHdr *mailHdr, bool bSet, + nsMsgMessageFlagType flag, + nsIOutputStream *fileStream); + bool DiskSpaceAvailableInStore(nsIFile *aFile, + uint64_t aSpaceRequested); +}; + +#endif diff --git a/mailnews/local/src/nsMsgMaildirStore.cpp b/mailnews/local/src/nsMsgMaildirStore.cpp new file mode 100644 index 000000000..7a4633367 --- /dev/null +++ b/mailnews/local/src/nsMsgMaildirStore.cpp @@ -0,0 +1,1453 @@ +/* -*- 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/. */ + +/** + Class for handling Maildir stores. +*/ + +#include "prprf.h" +#include "mozilla/Logging.h" +#include "msgCore.h" +#include "nsMsgMaildirStore.h" +#include "nsIMsgFolder.h" +#include "nsISimpleEnumerator.h" +#include "nsMsgFolderFlags.h" +#include "nsILocalMailIncomingServer.h" +#include "nsCOMArray.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsIMsgDatabase.h" +#include "nsNativeCharsetUtils.h" +#include "nsMsgUtils.h" +#include "nsMsgDBCID.h" +#include "nsIDBFolderInfo.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsMailHeaders.h" +#include "nsParseMailbox.h" +#include "nsIMailboxService.h" +#include "nsMsgLocalCID.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsITimer.h" +#include "nsIMailboxUrl.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgFilterPlugin.h" +#include "nsLocalUndoTxn.h" +#include "nsIMessenger.h" + +static PRLogModuleInfo* MailDirLog; + +nsMsgMaildirStore::nsMsgMaildirStore() +{ + MailDirLog = PR_NewLogModule("MailDirStore"); +} + +nsMsgMaildirStore::~nsMsgMaildirStore() +{ +} + +NS_IMPL_ISUPPORTS(nsMsgMaildirStore, nsIMsgPluggableStore) + +// Iterates over the folders in the "path" directory, and adds subfolders to +// parent for each Maildir folder found. +nsresult nsMsgMaildirStore::AddSubFolders(nsIMsgFolder *parent, nsIFile *path, + bool deep) +{ + nsCOMArray<nsIFile> currentDirEntries; + + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + nsresult rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr<nsISupports> aSupport; + directoryEnumerator->GetNext(getter_AddRefs(aSupport)); + nsCOMPtr<nsIFile> currentFile(do_QueryInterface(aSupport, &rv)); + if (currentFile) { + nsAutoString leafName; + currentFile->GetLeafName(leafName); + bool isDirectory = false; + currentFile->IsDirectory(&isDirectory); + // Make sure this really is a mail folder dir (i.e., a directory that + // contains cur and tmp sub-dirs, and not a .sbd or .mozmsgs dir). + if (isDirectory && !nsShouldIgnoreFile(leafName)) + currentDirEntries.AppendObject(currentFile); + } + } + + // add the folders + int32_t count = currentDirEntries.Count(); + for (int32_t i = 0; i < count; ++i) + { + nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]); + + nsAutoString leafName; + currentFile->GetLeafName(leafName); + + nsCOMPtr<nsIMsgFolder> child; + rv = parent->AddSubfolder(leafName, getter_AddRefs(child)); + if (child) + { + nsString folderName; + child->GetName(folderName); // try to get it from cache/db + if (folderName.IsEmpty()) + child->SetPrettyName(leafName); + if (deep) + { + nsCOMPtr<nsIFile> path; + rv = child->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + // Construct the .sbd directory path for the possible children of the + // folder. + GetDirectoryForFolder(path); + bool directory = false; + // Check that <folder>.sbd really is a directory. + path->IsDirectory(&directory); + if (directory) + AddSubFolders(child, path, true); + } + } + } + return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::DiscoverSubFolders(nsIMsgFolder *aParentFolder, + bool aDeep) +{ + NS_ENSURE_ARG_POINTER(aParentFolder); + + nsCOMPtr<nsIFile> path; + nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isServer, directory = false; + aParentFolder->GetIsServer(&isServer); + if (!isServer) + GetDirectoryForFolder(path); + + path->IsDirectory(&directory); + if (directory) + rv = AddSubFolders(aParentFolder, path, aDeep); + + return (rv == NS_MSG_FOLDER_EXISTS) ? NS_OK : rv; +} + +/** + * Create if missing a Maildir-style folder with "tmp" and "cur" subfolders + * but no "new" subfolder, because it doesn't make sense in the mail client + * context. ("new" directory is for messages on the server that haven't been +* seen by a mail client). + * aFolderName is already "safe" - it has been through NS_MsgHashIfNecessary. + */ +nsresult nsMsgMaildirStore::CreateMaildir(nsIFile *path) +{ + nsresult rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) + { + NS_WARNING("Could not create root directory for message folder"); + return rv; + } + + // Create tmp, cur leaves + nsCOMPtr<nsIFile> leaf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + leaf->InitWithFile(path); + + leaf->AppendNative(NS_LITERAL_CSTRING("tmp")); + rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) + { + NS_WARNING("Could not create tmp directory for message folder"); + return rv; + } + + leaf->SetNativeLeafName(NS_LITERAL_CSTRING("cur")); + rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) + { + NS_WARNING("Could not create cur directory for message folder"); + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::CreateFolder(nsIMsgFolder *aParent, + const nsAString &aFolderName, + nsIMsgFolder **aResult) +{ + NS_ENSURE_ARG_POINTER(aParent); + NS_ENSURE_ARG_POINTER(aResult); + if (aFolderName.IsEmpty()) + return NS_MSG_ERROR_INVALID_FOLDER_NAME; + + nsCOMPtr <nsIFile> path; + nsresult rv = aParent->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get a directory based on our current path + bool isServer; + aParent->GetIsServer(&isServer); + rv = CreateDirectoryForFolder(path, isServer); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure the new folder name is valid + nsAutoString safeFolderName(aFolderName); + NS_MsgHashIfNecessary(safeFolderName); + + path->Append(safeFolderName); + bool exists; + path->Exists(&exists); + if (exists) //check this because localized names are different from disk names + return NS_MSG_FOLDER_EXISTS; + + rv = CreateMaildir(path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> child; + // GetFlags and SetFlags in AddSubfolder will fail because we have no db at + // this point but mFlags is set. + rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child)); + if (!child || NS_FAILED(rv)) + { + path->Remove(true); // recursive + return rv; + } + + // Create an empty database for this mail folder, set its name from the user + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + nsCOMPtr<nsIMsgDatabase> unusedDB; + rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB)); + + if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) && + unusedDB) + { + //need to set the folder name + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (NS_SUCCEEDED(rv)) + folderInfo->SetMailboxName(safeFolderName); + + unusedDB->SetSummaryValid(true); + unusedDB->Close(true); + aParent->UpdateSummaryTotals(true); + } + else + { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("CreateFolder - failed creating db for new folder\n")); + path->Remove(true); // recursive + rv = NS_MSG_CANT_CREATE_FOLDER; + } + } + child.swap(*aResult); + return rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::HasSpaceAvailable(nsIMsgFolder *aFolder, + int64_t aSpaceRequested, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aFolder); + + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested); + if (!*aResult) + return NS_ERROR_FILE_DISK_FULL; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::IsSummaryFileValid(nsIMsgFolder *aFolder, + nsIMsgDatabase *aDB, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aDB); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + nsresult rv = dbFolderInfo->GetBooleanProperty("maildirValid", false, + aResult); + if (!*aResult) + { + nsCOMPtr<nsIFile> newFile; + rv = aFolder->GetFilePath(getter_AddRefs(newFile)); + NS_ENSURE_SUCCESS(rv, rv); + newFile->Append(NS_LITERAL_STRING("cur")); + + // If the "cur" sub-dir doesn't exist, and there are no messages + // in the db, then the folder is probably new and the db is valid. + bool exists; + newFile->Exists(&exists); + if (!exists) + { + int32_t numMessages; + dbFolderInfo->GetNumMessages(&numMessages); + if (!numMessages) + *aResult = true; + } + } + return rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::SetSummaryFileValid(nsIMsgFolder *aFolder, + nsIMsgDatabase *aDB, + bool aValid) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aDB); + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + NS_ENSURE_STATE(dbFolderInfo); + return dbFolderInfo->SetBooleanProperty("maildirValid", aValid); +} + +NS_IMETHODIMP nsMsgMaildirStore::DeleteFolder(nsIMsgFolder *aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + + // Delete Maildir structure + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pathFile->Remove(true); // recursive + AddDirectorySeparator(pathFile); + bool exists; + pathFile->Exists(&exists); + if (exists) + pathFile->Remove(true); + return rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::RenameFolder(nsIMsgFolder *aFolder, + const nsAString & aNewName, + nsIMsgFolder **aNewFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aNewFolder); + + // old path + nsCOMPtr<nsIFile> oldPathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // old sbd directory + nsCOMPtr<nsIFile> sbdPathFile; + uint32_t numChildren; + aFolder->GetNumSubFolders(&numChildren); + if (numChildren > 0) + { + sbdPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sbdPathFile->InitWithFile(oldPathFile); + NS_ENSURE_SUCCESS(rv, rv); + GetDirectoryForFolder(sbdPathFile); + } + + // old summary + nsCOMPtr<nsIFile> oldSummaryFile; + rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // Validate new name + nsAutoString safeName(aNewName); + NS_MsgHashIfNecessary(safeName); + + aFolder->ForceDBClosed(); + + // rename folder + rv = oldPathFile->MoveTo(nullptr, safeName); + NS_ENSURE_SUCCESS(rv, rv); + + if (numChildren > 0) + { + // rename "*.sbd" directory + nsAutoString sbdName = safeName; + sbdName += NS_LITERAL_STRING(FOLDER_SUFFIX); + sbdPathFile->MoveTo(nullptr, sbdName); + } + + // rename summary + nsAutoString summaryName(safeName); + summaryName += NS_LITERAL_STRING(SUMMARY_SUFFIX); + oldSummaryFile->MoveTo(nullptr, summaryName); + + nsCOMPtr<nsIMsgFolder> parentFolder; + rv = aFolder->GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) + return NS_ERROR_NULL_POINTER; + + return parentFolder->AddSubfolder(safeName, aNewFolder); +} + +NS_IMETHODIMP nsMsgMaildirStore::CopyFolder(nsIMsgFolder *aSrcFolder, + nsIMsgFolder *aDstFolder, + bool aIsMoveFolder, + nsIMsgWindow *aMsgWindow, + nsIMsgCopyServiceListener *aListener, + const nsAString &aNewName) +{ + NS_ENSURE_ARG_POINTER(aSrcFolder); + NS_ENSURE_ARG_POINTER(aDstFolder); + + nsAutoString folderName; + if (aNewName.IsEmpty()) + aSrcFolder->GetName(folderName); + else + folderName.Assign(aNewName); + + nsAutoString safeFolderName(folderName); + NS_MsgHashIfNecessary(safeFolderName); + nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder(do_QueryInterface(aSrcFolder)); + aSrcFolder->ForceDBClosed(); + + nsCOMPtr<nsIFile> oldPath; + nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIFile> summaryFile; + GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile)); + + nsCOMPtr<nsIFile> newPath; + rv = aDstFolder->GetFilePath(getter_AddRefs(newPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // create target directory based on our current path + bool isServer; + aDstFolder->GetIsServer(&isServer); + rv = CreateDirectoryForFolder(newPath, isServer); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> origPath; + oldPath->Clone(getter_AddRefs(origPath)); + + rv = oldPath->CopyTo(newPath, safeFolderName); + NS_ENSURE_SUCCESS(rv, rv); //will fail if a file by that name exists + + // Copy to dir can fail if file does not exist. If copy fails, we test + // if the file exists or not, if it does not that's ok, we continue + // without copying it. If it fails and file exist and is not zero sized + // there is real problem. + nsAutoString dbName(safeFolderName); + dbName += NS_LITERAL_STRING(SUMMARY_SUFFIX); + rv = summaryFile->CopyTo(newPath, dbName); + if (!NS_SUCCEEDED(rv)) + { + // Test if the file is not empty + bool exists; + int64_t fileSize; + summaryFile->Exists(&exists); + summaryFile->GetFileSize(&fileSize); + if (exists && fileSize > 0) + NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked! + // else case is file is zero sized, no need to copy it, + // not an error + // else case is file does not exist - not an error + } + + nsCOMPtr<nsIMsgFolder> newMsgFolder; + rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + newMsgFolder->SetPrettyName(folderName); + uint32_t flags; + aSrcFolder->GetFlags(&flags); + newMsgFolder->SetFlags(flags); + bool changed = false; + rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed); + if (changed) + aSrcFolder->AlertFilterChanged(aMsgWindow); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = aSrcFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy subfolders to the new location + nsresult copyStatus = NS_OK; + nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(do_QueryInterface(newMsgFolder, &rv)); + if (NS_SUCCEEDED(rv)) + { + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore && + NS_SUCCEEDED(copyStatus)) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item)); + if (!folder) + continue; + + copyStatus = localNewFolder->CopyFolderLocal(folder, false, aMsgWindow, + aListener); + // Test if the call succeeded, if not we have to stop recursive call + if (NS_FAILED(copyStatus)) + { + // Copy failed we have to notify caller to handle the error and stop + // moving the folders. In case this happens to the topmost level of + // recursive call, then we just need to break from the while loop and + // go to error handling code. + if (!aIsMoveFolder) + return copyStatus; + break; + } + } + } + + if (aIsMoveFolder && NS_SUCCEEDED(copyStatus)) + { + if (localNewFolder) + { + nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder)); + localNewFolder->OnCopyCompleted(srcSupport, true); + } + + // Notify that the folder that was dragged and dropped has been created. + // No need to do this for its subfolders - isMoveFolder will be true for folder. + aDstFolder->NotifyItemAdded(newMsgFolder); + + nsCOMPtr<nsIMsgFolder> msgParent; + aSrcFolder->GetParent(getter_AddRefs(msgParent)); + aSrcFolder->SetParent(nullptr); + if (msgParent) + { + // The files have already been moved, so delete storage false + msgParent->PropagateDelete(aSrcFolder, false, aMsgWindow); + oldPath->Remove(true); + nsCOMPtr<nsIMsgDatabase> srcDB; // we need to force closed the source db + aSrcFolder->Delete(); + + nsCOMPtr<nsIFile> parentPath; + rv = msgParent->GetFilePath(getter_AddRefs(parentPath)); + NS_ENSURE_SUCCESS(rv,rv); + + AddDirectorySeparator(parentPath); + nsCOMPtr<nsISimpleEnumerator> children; + parentPath->GetDirectoryEntries(getter_AddRefs(children)); + bool more; + // checks if the directory is empty or not + if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more) + parentPath->Remove(true); + } + } + else + { + // This is the case where the copy of a subfolder failed. + // We have to delete the newDirectory tree to make a "rollback". + // Someone should add a popup to warn the user that the move was not + // possible. + if (aIsMoveFolder && NS_FAILED(copyStatus)) + { + nsCOMPtr<nsIMsgFolder> msgParent; + newMsgFolder->ForceDBClosed(); + newMsgFolder->GetParent(getter_AddRefs(msgParent)); + newMsgFolder->SetParent(nullptr); + if (msgParent) + { + msgParent->PropagateDelete(newMsgFolder, false, aMsgWindow); + newMsgFolder->Delete(); + newMsgFolder->ForceDBClosed(); + AddDirectorySeparator(newPath); + newPath->Remove(true); //berkeley mailbox + } + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::GetNewMsgOutputStream(nsIMsgFolder *aFolder, + nsIMsgDBHdr **aNewMsgHdr, + bool *aReusable, + nsIOutputStream **aResult) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aNewMsgHdr); + NS_ENSURE_ARG_POINTER(aReusable); + NS_ENSURE_ARG_POINTER(aResult); + + *aReusable = false; // message per file + + nsCOMPtr<nsIMsgDatabase> db; + aFolder->GetMsgDatabase(getter_AddRefs(db)); + if (!db) + NS_ERROR("no db"); + + nsresult rv; + + if (!*aNewMsgHdr) + { + rv = db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr); + NS_ENSURE_SUCCESS(rv, rv); + + } + (*aNewMsgHdr)->SetMessageOffset(0); + // path to the message download folder + nsCOMPtr<nsIFile> newFile; + rv = aFolder->GetFilePath(getter_AddRefs(newFile)); + NS_ENSURE_SUCCESS(rv, rv); + newFile->Append(NS_LITERAL_STRING("tmp")); + + // let's check if the folder exists + bool exists; + newFile->Exists(&exists); + if (!exists) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetNewMsgOutputStream - tmp subfolder does not exist!!\n")); + rv = newFile->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + // generate new file name + nsAutoCString newName; + newName.AppendInt(static_cast<int64_t>(PR_Now())); + newFile->AppendNative(newName); + // CreateUnique, in case we get more than one message per millisecond :-) + rv = newFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + newFile->GetNativeLeafName(newName); + // save the file name in the message header - otherwise no way to retrieve it + (*aNewMsgHdr)->SetStringProperty("storeToken", newName.get()); + return MsgNewBufferedFileOutputStream(aResult, newFile, + PR_WRONLY | PR_CREATE_FILE, 00600); +} + +NS_IMETHODIMP +nsMsgMaildirStore::DiscardNewMessage(nsIOutputStream *aOutputStream, + nsIMsgDBHdr *aNewHdr) +{ + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_ENSURE_ARG_POINTER(aNewHdr); + + aOutputStream->Close(); + // file path is stored in message header property "storeToken" + nsAutoCString fileName; + aNewHdr->GetStringProperty("storeToken", getter_Copies(fileName)); + if (fileName.IsEmpty()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> path; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + // path to the message download folder + path->Append(NS_LITERAL_STRING("tmp")); + path->AppendNative(fileName); + + return path->Remove(false); +} + +NS_IMETHODIMP +nsMsgMaildirStore::FinishNewMessage(nsIOutputStream *aOutputStream, + nsIMsgDBHdr *aNewHdr) +{ + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_ENSURE_ARG_POINTER(aNewHdr); + + aOutputStream->Close(); + + nsCOMPtr<nsIFile> folderPath; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // file path is stored in message header property + nsAutoCString fileName; + aNewHdr->GetStringProperty("storeToken", getter_Copies(fileName)); + if (fileName.IsEmpty()) + { + NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!\n"); + return NS_ERROR_FAILURE; + } + + // path to the new destination + nsCOMPtr<nsIFile> toPath; + folderPath->Clone(getter_AddRefs(toPath)); + toPath->Append(NS_LITERAL_STRING("cur")); + + // let's check if the folder exists + bool exists; + toPath->Exists(&exists); + if (!exists) + { + rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + // path to the downloaded message + nsCOMPtr<nsIFile> fromPath; + folderPath->Clone(getter_AddRefs(fromPath)); + fromPath->Append(NS_LITERAL_STRING("tmp")); + fromPath->AppendNative(fileName); + + // let's check if the tmp file exists + fromPath->Exists(&exists); + if (!exists) + { + // Perhaps the message has already moved. See bug 1028372 to fix this. + toPath->AppendNative(fileName); + toPath->Exists(&exists); + if (exists) // then there is nothing to do + return NS_OK; + + NS_ERROR("FinishNewMessage - oops! file does not exist!"); + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + } + + nsCOMPtr<nsIFile> existingPath; + toPath->Clone(getter_AddRefs(existingPath)); + existingPath->AppendNative(fileName); + existingPath->Exists(&exists); + + if (exists) { + rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + existingPath->GetNativeLeafName(fileName); + aNewHdr->SetStringProperty("storeToken", fileName.get()); + } + + return fromPath->MoveToNative(toPath, fileName); +} + +NS_IMETHODIMP +nsMsgMaildirStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr *aHdr, + nsIMsgFolder *aDestFolder, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aHdr); + NS_ENSURE_ARG_POINTER(aDestFolder); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIFile> folderPath; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // file path is stored in message header property + nsAutoCString fileName; + aHdr->GetStringProperty("storeToken", getter_Copies(fileName)); + if (fileName.IsEmpty()) + { + NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!\n"); + return NS_ERROR_FAILURE; + } + + // path to the downloaded message + nsCOMPtr<nsIFile> fromPath; + folderPath->Clone(getter_AddRefs(fromPath)); + fromPath->Append(NS_LITERAL_STRING("cur")); + fromPath->AppendNative(fileName); + + // let's check if the tmp file exists + bool exists; + fromPath->Exists(&exists); + if (!exists) + { + NS_ERROR("FinishNewMessage - oops! file does not exist!"); + return NS_ERROR_FAILURE; + } + + // move to the "cur" subfolder + nsCOMPtr<nsIFile> toPath; + aDestFolder->GetFilePath(getter_AddRefs(folderPath)); + folderPath->Clone(getter_AddRefs(toPath)); + toPath->Append(NS_LITERAL_STRING("cur")); + + // let's check if the folder exists + toPath->Exists(&exists); + if (!exists) + { + rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIMsgDatabase> destMailDB; + rv = aDestFolder->GetMsgDatabase(getter_AddRefs(destMailDB)); + NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv), + "failed to open mail db moving message"); + + nsCOMPtr<nsIMsgDBHdr> newHdr; + if (destMailDB) + rv = destMailDB->CopyHdrFromExistingHdr(nsMsgKey_None, aHdr, true, + getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && !newHdr) + rv = NS_ERROR_UNEXPECTED; + + if (NS_FAILED(rv)) { + aDestFolder->ThrowAlertMsg("filterFolderHdrAddFailed", nullptr); + return rv; + } + + nsCOMPtr<nsIFile> existingPath; + toPath->Clone(getter_AddRefs(existingPath)); + existingPath->AppendNative(fileName); + existingPath->Exists(&exists); + + if (exists) { + rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + existingPath->GetNativeLeafName(fileName); + newHdr->SetStringProperty("storeToken", fileName.get()); + } + + rv = fromPath->MoveToNative(toPath, fileName); + *aResult = NS_SUCCEEDED(rv); + if (NS_FAILED(rv)) + aDestFolder->ThrowAlertMsg("filterFolderWriteFailed", nullptr); + + if (NS_FAILED(rv)) { + if (destMailDB) + destMailDB->Close(true); + + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + bool movedMsgIsNew = false; + // if we have made it this far then the message has successfully been + // written to the new folder now add the header to the destMailDB. + + uint32_t newFlags; + newHdr->GetFlags(&newFlags); + nsMsgKey msgKey; + newHdr->GetMessageKey(&msgKey); + if (!(newFlags & nsMsgMessageFlags::Read)) + { + nsCString junkScoreStr; + (void) newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (atoi(junkScoreStr.get()) != nsIJunkMailPlugin::IS_SPAM_SCORE) { + newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + destMailDB->AddToNewList(msgKey); + movedMsgIsNew = true; + } + } + + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgAdded(newHdr); + + if (movedMsgIsNew) { + aDestFolder->SetHasNewMessages(true); + + // Notify the message was moved. + if (notifier) { + notifier->NotifyItemEvent(folder, + NS_LITERAL_CSTRING("UnincorporatedMessageMoved"), + newHdr); + } + } + + nsCOMPtr<nsIMsgDatabase> sourceDB; + rv = folder->GetMsgDatabase(getter_AddRefs(sourceDB)); + + if (NS_SUCCEEDED(rv) && sourceDB) + sourceDB->RemoveHeaderMdbRow(aHdr); + + destMailDB->SetSummaryValid(true); + aDestFolder->UpdateSummaryTotals(true); + destMailDB->Commit(nsMsgDBCommitType::kLargeCommit); + return rv; +} + +NS_IMETHODIMP +nsMsgMaildirStore::GetMsgInputStream(nsIMsgFolder *aMsgFolder, + const nsACString &aMsgToken, + int64_t *aOffset, + nsIMsgDBHdr *aMsgHdr, + bool *aReusable, + nsIInputStream **aResult) +{ + NS_ENSURE_ARG_POINTER(aMsgFolder); + NS_ENSURE_ARG_POINTER(aOffset); + NS_ENSURE_ARG_POINTER(aResult); + + *aReusable = false; // message per file + *aOffset = 0; + + // construct path to file + nsCOMPtr<nsIFile> path; + nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aMsgToken.IsEmpty()) + { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetMsgInputStream - empty storeToken!!\n")); + return NS_ERROR_FAILURE; + } + + path->Append(NS_LITERAL_STRING("cur")); + + // let's check if the folder exists + bool exists; + path->Exists(&exists); + if (!exists) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetMsgInputStream - oops! cur subfolder does not exist!\n")); + rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + path->AppendNative(aMsgToken); + return NS_NewLocalFileInputStream(aResult, path); +} + +NS_IMETHODIMP nsMsgMaildirStore::DeleteMessages(nsIArray *aHdrArray) +{ + uint32_t messageCount; + nsresult rv = aHdrArray->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgFolder> folder; + + for (uint32_t i = 0; i < messageCount; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, i, &rv); + if (NS_FAILED(rv)) + continue; + msgHdr->GetFolder(getter_AddRefs(folder)); + nsCOMPtr<nsIFile> path; + rv = folder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString fileName; + msgHdr->GetStringProperty("storeToken", getter_Copies(fileName)); + + if (fileName.IsEmpty()) + { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("DeleteMessages - empty storeToken!!\n")); + // Perhaps an offline store has not downloaded this particular message. + continue; + } + + path->Append(NS_LITERAL_STRING("cur")); + path->AppendNative(fileName); + + // Let's check if the message exists. + bool exists; + path->Exists(&exists); + if (!exists) + { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("DeleteMessages - file does not exist !!\n")); + // Perhaps an offline store has not downloaded this particular message. + continue; + } + path->Remove(false); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::CopyMessages(bool aIsMove, nsIArray *aHdrArray, + nsIMsgFolder *aDstFolder, + nsIMsgCopyServiceListener *aListener, + nsIArray **aDstHdrs, + nsITransaction **aUndoAction, + bool *aCopyDone) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + NS_ENSURE_ARG_POINTER(aDstFolder); + NS_ENSURE_ARG_POINTER(aCopyDone); + NS_ENSURE_ARG_POINTER(aUndoAction); + + *aCopyDone = false; + + nsCOMPtr<nsIMsgFolder> srcFolder; + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, 0, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgHdr->GetFolder(getter_AddRefs(srcFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + // Both source and destination folders must use maildir type store. + nsCOMPtr<nsIMsgPluggableStore> srcStore; + nsAutoCString srcType; + srcFolder->GetMsgStore(getter_AddRefs(srcStore)); + if (srcStore) + srcStore->GetStoreType(srcType); + nsCOMPtr<nsIMsgPluggableStore> dstStore; + nsAutoCString dstType; + aDstFolder->GetMsgStore(getter_AddRefs(dstStore)); + if (dstStore) + dstStore->GetStoreType(dstType); + if (!srcType.EqualsLiteral("maildir") || !dstType.EqualsLiteral("maildir")) + return NS_OK; + + // Both source and destination must be local folders. In theory we could + // do efficient copies of the offline store of IMAP, but this is not + // supported yet. For that, we need to deal with both correct handling + // of deletes from the src server, and msgKey = UIDL in the dst folder. + nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder(do_QueryInterface(aDstFolder)); + if (!destLocalFolder) + return NS_OK; + nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder(do_QueryInterface(srcFolder)); + if (!srcLocalFolder) + return NS_OK; + + // We should be able to use a file move for an efficient copy. + + nsCOMPtr<nsIFile> destFolderPath; + nsCOMPtr<nsIMsgDatabase> destDB; + aDstFolder->GetMsgDatabase(getter_AddRefs(destDB)); + rv = aDstFolder->GetFilePath(getter_AddRefs(destFolderPath)); + NS_ENSURE_SUCCESS(rv, rv); + destFolderPath->Append(NS_LITERAL_STRING("cur")); + + nsCOMPtr<nsIFile> srcFolderPath; + rv = srcFolder->GetFilePath(getter_AddRefs(srcFolderPath)); + NS_ENSURE_SUCCESS(rv, rv); + srcFolderPath->Append(NS_LITERAL_STRING("cur")); + + nsCOMPtr<nsIMsgDatabase> srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn; + NS_ENSURE_TRUE(msgTxn, NS_ERROR_OUT_OF_MEMORY); + if (NS_SUCCEEDED(msgTxn->Init(srcFolder, aDstFolder, aIsMove))) + { + if (aIsMove) + msgTxn->SetTransactionType(nsIMessenger::eMoveMsg); + else + msgTxn->SetTransactionType(nsIMessenger::eCopyMsg); + } + + if (aListener) + aListener->OnStartCopy(); + + nsCOMPtr<nsIMutableArray> dstHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t messageCount; + rv = aHdrArray->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < messageCount; i++) + { + nsCOMPtr<nsIMsgDBHdr> srcHdr = do_QueryElementAt(aHdrArray, i, &rv); + if (NS_FAILED(rv)) + { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("srcHdr null\n")); + continue; + } + nsMsgKey srcKey; + srcHdr->GetMessageKey(&srcKey); + msgTxn->AddSrcKey(srcKey); + nsAutoCString fileName; + srcHdr->GetStringProperty("storeToken", getter_Copies(fileName)); + if (fileName.IsEmpty()) + { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetMsgInputStream - empty storeToken!!\n")); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> srcFile; + rv = srcFolderPath->Clone(getter_AddRefs(srcFile)); + NS_ENSURE_SUCCESS(rv, rv); + srcFile->AppendNative(fileName); + + nsCOMPtr<nsIFile> destFile; + destFolderPath->Clone(getter_AddRefs(destFile)); + destFile->AppendNative(fileName); + bool exists; + destFile->Exists(&exists); + if (exists) + { + rv = destFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + destFile->GetNativeLeafName(fileName); + } + if (aIsMove) + rv = srcFile->MoveToNative(destFolderPath, fileName); + else + rv = srcFile->CopyToNative(destFolderPath, fileName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgDBHdr> destHdr; + if (destDB) + { + rv = destDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, true, getter_AddRefs(destHdr)); + NS_ENSURE_SUCCESS(rv, rv); + destHdr->SetStringProperty("storeToken", fileName.get()); + dstHdrs->AppendElement(destHdr, false); + nsMsgKey dstKey; + destHdr->GetMessageKey(&dstKey); + msgTxn->AddDstKey(dstKey); + if (aListener) + aListener->SetMessageKey(dstKey); + } + } + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgsMoveCopyCompleted(aIsMove, aHdrArray, aDstFolder, + dstHdrs); + + // For now, we only support local dest folders, and for those we are done and + // can delete the messages. Perhaps this should be moved into the folder + // when we try to support other folder types. + if (aIsMove) + { + for (uint32_t i = 0; i < messageCount; ++i) + { + nsCOMPtr<nsIMsgDBHdr> msgDBHdr(do_QueryElementAt(aHdrArray, i, &rv)); + rv = srcDB->DeleteHeader(msgDBHdr, nullptr, false, true); + } + } + + *aCopyDone = true; + nsCOMPtr<nsISupports> srcSupports(do_QueryInterface(srcFolder)); + if (destLocalFolder) + destLocalFolder->OnCopyCompleted(srcSupports, true); + if (aListener) + aListener->OnStopCopy(NS_OK); + msgTxn.forget(aUndoAction); + dstHdrs.forget(aDstHdrs); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::GetSupportsCompaction(bool *aSupportsCompaction) +{ + NS_ENSURE_ARG_POINTER(aSupportsCompaction); + *aSupportsCompaction = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::CompactFolder(nsIMsgFolder *aFolder, + nsIUrlListener *aListener, + nsIMsgWindow *aMsgWindow) +{ + return NS_OK; +} + +class MaildirStoreParser +{ +public: + MaildirStoreParser(nsIMsgFolder *aFolder, nsIMsgDatabase *aMsgDB, + nsISimpleEnumerator *aDirectoryEnumerator, + nsIUrlListener *aUrlListener); + virtual ~MaildirStoreParser(); + + nsresult ParseNextMessage(nsIFile *aFile); + static void TimerCallback(nsITimer *aTimer, void *aClosure); + nsresult StartTimer(); + + nsCOMPtr<nsISimpleEnumerator> m_directoryEnumerator; + nsCOMPtr<nsIMsgFolder> m_folder; + nsCOMPtr<nsIMsgDatabase> m_db; + nsCOMPtr<nsITimer> m_timer; + nsCOMPtr<nsIUrlListener> m_listener; +}; + +MaildirStoreParser::MaildirStoreParser(nsIMsgFolder *aFolder, + nsIMsgDatabase *aMsgDB, + nsISimpleEnumerator *aDirEnum, + nsIUrlListener *aUrlListener) +{ + m_folder = aFolder; + m_db = aMsgDB; + m_directoryEnumerator = aDirEnum; + m_listener = aUrlListener; +} + +MaildirStoreParser::~MaildirStoreParser() +{ +} + +nsresult MaildirStoreParser::ParseNextMessage(nsIFile *aFile) +{ + nsresult rv; + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIMsgParseMailMsgState> msgParser = + do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgParser->SetMailDB(m_db); + nsCOMPtr<nsIMsgDBHdr> newMsgHdr; + rv = m_db->CreateNewHdr(nsMsgKey_None, getter_AddRefs(newMsgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + newMsgHdr->SetMessageOffset(0); + + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + if (NS_SUCCEEDED(rv) && inputStream) + { + nsMsgLineStreamBuffer *inputStreamBuffer = + new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false); + int64_t fileSize; + aFile->GetFileSize(&fileSize); + msgParser->SetNewMsgHdr(newMsgHdr); + msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState); + msgParser->SetEnvelopePos(0); + bool needMoreData = false; + char * newLine = nullptr; + uint32_t numBytesInLine = 0; + // we only have to read the headers, because we know the message size + // from the file size. So we can do this in one time slice. + do + { + newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine, + needMoreData); + if (newLine) + { + msgParser->ParseAFolderLine(newLine, numBytesInLine); + NS_Free(newLine); + } + } while (newLine && numBytesInLine > 0); + + msgParser->FinishHeader(); + // A single message needs to be less than 4GB + newMsgHdr->SetMessageSize((uint32_t) fileSize); + m_db->AddNewHdrToDB(newMsgHdr, true); + nsAutoCString storeToken; + aFile->GetNativeLeafName(storeToken); + newMsgHdr->SetStringProperty("storeToken", storeToken.get()); + } + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +void MaildirStoreParser::TimerCallback(nsITimer *aTimer, void *aClosure) +{ + MaildirStoreParser *parser = (MaildirStoreParser *) aClosure; + bool hasMore; + parser->m_directoryEnumerator->HasMoreElements(&hasMore); + if (!hasMore) + { + nsCOMPtr<nsIMsgPluggableStore> store; + parser->m_folder->GetMsgStore(getter_AddRefs(store)); + parser->m_timer->Cancel(); + parser->m_db->SetSummaryValid(true); +// store->SetSummaryFileValid(parser->m_folder, parser->m_db, true); + if (parser->m_listener) + { + nsresult rv; + nsCOMPtr<nsIMailboxUrl> mailboxurl = + do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mailboxurl) + { + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl); + url->SetUpdatingFolder(true); + nsAutoCString uriSpec("mailbox://"); + // ### TODO - what if SetSpec fails? + (void) url->SetSpec(uriSpec); + parser->m_listener->OnStopRunningUrl(url, NS_OK); + } + } + // Parsing complete and timer cancelled, so we release the parser object. + delete parser; + return; + } + nsCOMPtr<nsISupports> aSupport; + parser->m_directoryEnumerator->GetNext(getter_AddRefs(aSupport)); + nsresult rv; + nsCOMPtr<nsIFile> currentFile(do_QueryInterface(aSupport, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + parser->ParseNextMessage(currentFile); + // ### TODO - what if this fails? +} + +nsresult MaildirStoreParser::StartTimer() +{ + nsresult rv; + m_timer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_timer->InitWithFuncCallback(TimerCallback, (void *) this, 0, + nsITimer::TYPE_REPEATING_SLACK); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::RebuildIndex(nsIMsgFolder *aFolder, + nsIMsgDatabase *aMsgDB, + nsIMsgWindow *aMsgWindow, + nsIUrlListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aFolder); + // This code needs to iterate over the maildir files, and parse each + // file and add a msg hdr to the db for the file. + nsCOMPtr<nsIFile> path; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + path->Append(NS_LITERAL_STRING("cur")); + + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + MaildirStoreParser *fileParser = new MaildirStoreParser(aFolder, aMsgDB, + directoryEnumerator, + aListener); + NS_ENSURE_TRUE(fileParser, NS_ERROR_OUT_OF_MEMORY); + fileParser->StartTimer(); + ResetForceReparse(aMsgDB); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::ChangeFlags(nsIArray *aHdrArray, + uint32_t aFlags, + bool aSet) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + + uint32_t messageCount; + nsresult rv = aHdrArray->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < messageCount; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(aHdrArray, i, &rv); + // get output stream for header + nsCOMPtr<nsIOutputStream> outputStream; + rv = GetOutputStream(msgHdr, outputStream); + NS_ENSURE_SUCCESS(rv, rv); + // Seek to x-mozilla-status offset and rewrite value. + rv = UpdateFolderFlag(msgHdr, aSet, aFlags, outputStream); + if (NS_FAILED(rv)) + NS_WARNING("updateFolderFlag failed"); + } + return NS_OK; +} + +// get output stream from header +nsresult +nsMsgMaildirStore::GetOutputStream(nsIMsgDBHdr *aHdr, + nsCOMPtr<nsIOutputStream> &aOutputStream) +{ + // file name is stored in message header property "storeToken" + nsAutoCString fileName; + aHdr->GetStringProperty("storeToken", getter_Copies(fileName)); + if (fileName.IsEmpty()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> folderPath; + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> maildirFile; + folderPath->Clone(getter_AddRefs(maildirFile)); + maildirFile->Append(NS_LITERAL_STRING("cur")); + maildirFile->AppendNative(fileName); + + return MsgGetFileStream(maildirFile, getter_AddRefs(aOutputStream)); +} + +NS_IMETHODIMP nsMsgMaildirStore::ChangeKeywords(nsIArray *aHdrArray, + const nsACString &aKeywords, + bool aAdd) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + NS_ENSURE_ARG_POINTER(aHdrArray); + nsCOMPtr<nsIOutputStream> outputStream; + nsCOMPtr<nsISeekableStream> seekableStream; + + uint32_t messageCount; + nsresult rv = aHdrArray->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + if (!messageCount) + return NS_ERROR_INVALID_ARG; + + nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>); + NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); + + nsTArray<nsCString> keywordArray; + ParseString(aKeywords, ' ', keywordArray); + + for (uint32_t i = 0; i < messageCount; ++i) // for each message + { + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aHdrArray, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // get output stream for header + nsCOMPtr<nsIOutputStream> outputStream; + rv = GetOutputStream(message, outputStream); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIInputStream> inputStream = do_QueryInterface(outputStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsISeekableStream> seekableStream(do_QueryInterface(inputStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t statusOffset = 0; + (void)message->GetStatusOffset(&statusOffset); + uint64_t desiredOffset = statusOffset; + + ChangeKeywordsHelper(message, desiredOffset, lineBuffer, keywordArray, + aAdd, outputStream, seekableStream, inputStream); + if (inputStream) + inputStream->Close(); + // ### TODO - if growKeywords property is set on the message header, + // we need to rewrite the message file with extra room for the keywords, + // or schedule some sort of background task to do this. + } + lineBuffer = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::GetStoreType(nsACString& aType) +{ + aType.AssignLiteral("maildir"); + return NS_OK; +} + +/** + * Finds the directory associated with this folder. That is if the path is + * c:\Inbox, it will return c:\Inbox.sbd if it succeeds. Path is strictly + * an out parameter. + */ +nsresult nsMsgMaildirStore::GetDirectoryForFolder(nsIFile *path) +{ + // add directory separator to the path + nsAutoString leafName; + path->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + return path->SetLeafName(leafName); +} + +nsresult nsMsgMaildirStore::CreateDirectoryForFolder(nsIFile *path, + bool aIsServer) +{ + nsresult rv = NS_OK; + if (!aIsServer) + { + rv = GetDirectoryForFolder(path); + NS_ENSURE_SUCCESS(rv, rv); + } + bool pathIsDirectory = false; + path->IsDirectory(&pathIsDirectory); + if (!pathIsDirectory) + { + bool pathExists; + path->Exists(&pathExists); + //If for some reason there's a file with the directory separator + //then we are going to fail. + rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY : + path->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + return rv; +} diff --git a/mailnews/local/src/nsMsgMaildirStore.h b/mailnews/local/src/nsMsgMaildirStore.h new file mode 100644 index 000000000..f15944e5d --- /dev/null +++ b/mailnews/local/src/nsMsgMaildirStore.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +/** + Class for handling Maildir stores. +*/ + +#ifndef nsMsgMaildirStore_h__ +#define nsMsgMaildirStore_h__ + +#include "nsMsgLocalStoreUtils.h" +#include "nsIFile.h" +#include "nsMsgMessageFlags.h" + +class nsMsgMaildirStore final : public nsMsgLocalStoreUtils, nsIMsgPluggableStore +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPLUGGABLESTORE + + nsMsgMaildirStore(); + +private: + ~nsMsgMaildirStore(); + +protected: + nsresult GetDirectoryForFolder(nsIFile *path); + nsresult CreateDirectoryForFolder(nsIFile *path, bool aIsServer); + + nsresult CreateMaildir(nsIFile *path); + nsresult AddSubFolders(nsIMsgFolder *parent, nsIFile *path, bool deep); + nsresult GetOutputStream(nsIMsgDBHdr *aHdr, + nsCOMPtr<nsIOutputStream> &aOutputStream); + +}; +#endif diff --git a/mailnews/local/src/nsNoIncomingServer.cpp b/mailnews/local/src/nsNoIncomingServer.cpp new file mode 100644 index 000000000..1c18bf7fd --- /dev/null +++ b/mailnews/local/src/nsNoIncomingServer.cpp @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // pre-compiled headers + +#include "prmem.h" +#include "plstr.h" +#include "prprf.h" +#include "nsNoIncomingServer.h" +#include "nsMsgLocalCID.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgMailSession.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgAccountManager.h" +#include "nsIPop3IncomingServer.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS_INHERITED(nsNoIncomingServer, + nsMsgIncomingServer, + nsINoIncomingServer, + nsILocalMailIncomingServer) + +nsNoIncomingServer::nsNoIncomingServer() +{ +} + +nsNoIncomingServer::~nsNoIncomingServer() +{ +} + +NS_IMETHODIMP +nsNoIncomingServer::GetLocalStoreType(nsACString& type) +{ + type.AssignLiteral("mailbox"); + return NS_OK; +} + +NS_IMETHODIMP +nsNoIncomingServer::GetLocalDatabaseType(nsACString& type) +{ + type.AssignLiteral("mailbox"); + return NS_OK; +} + +NS_IMETHODIMP +nsNoIncomingServer::GetAccountManagerChrome(nsAString& aResult) +{ + aResult.AssignLiteral("am-serverwithnoidentities.xul"); + return NS_OK; +} + +NS_IMETHODIMP +nsNoIncomingServer::SetFlagsOnDefaultMailboxes() +{ + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(rootFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // None server may have an inbox if it's deferred to, + // or if it's the smart mailboxes account. + localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse); + + return NS_OK; +} + +// TODO: make this work with maildir message store, bug 890742. +NS_IMETHODIMP nsNoIncomingServer::CopyDefaultMessages(const char *folderNameOnDisk) +{ + NS_ENSURE_ARG(folderNameOnDisk); + + nsresult rv; + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get defaults directory for messenger files. MailSession service appends 'messenger' to the + // the app defaults folder and returns it. Locale will be added to the path, if there is one. + nsCOMPtr<nsIFile> defaultMessagesFile; + rv = mailSession->GetDataFilesDir("messenger", getter_AddRefs(defaultMessagesFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // check if bin/defaults/messenger/<folderNameOnDisk> + // (or bin/defaults/messenger/<locale>/<folderNameOnDisk> if we had a locale provide) exists. + // it doesn't have to exist. if it doesn't, return + rv = defaultMessagesFile->AppendNative(nsDependentCString(folderNameOnDisk)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = defaultMessagesFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_OK; + + nsCOMPtr<nsIFile> parentDir; + rv = GetLocalPath(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // check if parentDir/<folderNameOnDisk> exists + { + nsCOMPtr<nsIFile> testDir; + rv = parentDir->Clone(getter_AddRefs(testDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = testDir->AppendNative(nsDependentCString(folderNameOnDisk)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = testDir->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + } + + // if it exists add to the end, else copy + if (exists) + { +#ifdef DEBUG + printf("append default %s (unimplemented)\n", folderNameOnDisk); +#endif + // todo for bug #1181 (the bug ID seems wrong...) + // open folderFile, seek to end + // read defaultMessagesFile, write to folderFile + } + else { +#ifdef DEBUG + printf("copy default %s\n",folderNameOnDisk); +#endif + rv = defaultMessagesFile->CopyTo(parentDir, EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + + +NS_IMETHODIMP nsNoIncomingServer::CreateDefaultMailboxes() +{ + nsresult rv; + bool isHidden = false; + GetHidden(&isHidden); + if (isHidden) + return NS_OK; + + // notice, no Inbox, unless we're deferred to... + bool isDeferredTo; + if (NS_SUCCEEDED(GetIsDeferredTo(&isDeferredTo)) && isDeferredTo) + { + rv = CreateLocalFolder(NS_LITERAL_STRING("Inbox")); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = CreateLocalFolder(NS_LITERAL_STRING("Trash")); + NS_ENSURE_SUCCESS(rv, rv); + + // copy the default templates into the Templates folder + rv = CopyDefaultMessages("Templates"); + NS_ENSURE_SUCCESS(rv, rv); + + return CreateLocalFolder(NS_LITERAL_STRING("Unsent Messages")); +} + +NS_IMETHODIMP +nsNoIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIMsgFolder *aInbox, nsIURI **aResult) +{ + nsCOMArray<nsIPop3IncomingServer> deferredServers; + nsresult rv = GetDeferredServers(this, deferredServers); + NS_ENSURE_SUCCESS(rv, rv); + if (!deferredServers.IsEmpty()) + { + rv = deferredServers[0]->DownloadMailFromServers(deferredServers.Elements(), + deferredServers.Length(), aMsgWindow, aInbox, aUrlListener); + } + // listener might be counting on us to send a notification. + else if (aUrlListener) + aUrlListener->OnStopRunningUrl(nullptr, NS_OK); + return rv; +} + + +NS_IMETHODIMP +nsNoIncomingServer::GetCanSearchMessages(bool *canSearchMessages) +{ + NS_ENSURE_ARG_POINTER(canSearchMessages); + *canSearchMessages = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNoIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) +{ + NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff); + *aServerRequiresPasswordForBiff = false; // for local folders, we don't require a password + return NS_OK; +} + +NS_IMETHODIMP +nsNoIncomingServer::GetSortOrder(int32_t* aSortOrder) +{ + NS_ENSURE_ARG_POINTER(aSortOrder); + *aSortOrder = 200000000; + return NS_OK; +} + diff --git a/mailnews/local/src/nsNoIncomingServer.h b/mailnews/local/src/nsNoIncomingServer.h new file mode 100644 index 000000000..809531627 --- /dev/null +++ b/mailnews/local/src/nsNoIncomingServer.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef __nsNoIncomingServer_h +#define __nsNoIncomingServer_h + +#include "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsINoIncomingServer.h" +#include "nsILocalMailIncomingServer.h" +#include "nsMsgIncomingServer.h" +#include "nsMailboxServer.h" + +/* get some implementation from nsMsgIncomingServer */ +class nsNoIncomingServer : public nsMailboxServer, + public nsINoIncomingServer, + public nsILocalMailIncomingServer + +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSINOINCOMINGSERVER + NS_DECL_NSILOCALMAILINCOMINGSERVER + + nsNoIncomingServer(); + + NS_IMETHOD GetLocalStoreType(nsACString& type) override; + NS_IMETHOD GetLocalDatabaseType(nsACString& type) override; + NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override; + NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override; + NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override; + NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override; + +private: + virtual ~nsNoIncomingServer(); +}; + + +#endif diff --git a/mailnews/local/src/nsNoneService.cpp b/mailnews/local/src/nsNoneService.cpp new file mode 100644 index 000000000..dad56c063 --- /dev/null +++ b/mailnews/local/src/nsNoneService.cpp @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... + +#include "nsNoneService.h" +#include "nsINoIncomingServer.h" +#include "nsINoneService.h" +#include "nsIMsgProtocolInfo.h" + +#include "nsMsgLocalCID.h" +#include "nsMsgBaseCID.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" + +#include "nsIDirectoryService.h" +#include "nsMailDirServiceDefs.h" + +#define PREF_MAIL_ROOT_NONE "mail.root.none" // old - for backward compatibility only +#define PREF_MAIL_ROOT_NONE_REL "mail.root.none-rel" + +nsNoneService::nsNoneService() +{ +} + +nsNoneService::~nsNoneService() +{} + +NS_IMPL_ISUPPORTS(nsNoneService, nsINoneService, nsIMsgProtocolInfo) + +NS_IMETHODIMP +nsNoneService::SetDefaultLocalPath(nsIFile *aPath) +{ + NS_ENSURE_ARG(aPath); + return NS_SetPersistentFile(PREF_MAIL_ROOT_NONE_REL, PREF_MAIL_ROOT_NONE, aPath); +} + +NS_IMETHODIMP +nsNoneService::GetDefaultLocalPath(nsIFile ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + bool havePref; + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_NONE_REL, + PREF_MAIL_ROOT_NONE, + NS_APP_MAIL_50_DIR, + havePref, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) return rv; + + bool exists; + rv = localFile->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + + if (!havePref || !exists) + { + rv = NS_SetPersistentFile(PREF_MAIL_ROOT_NONE_REL, PREF_MAIL_ROOT_NONE, localFile); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref."); + } + + NS_IF_ADDREF(*aResult = localFile); + return NS_OK; + +} + + +NS_IMETHODIMP +nsNoneService::GetServerIID(nsIID* *aServerIID) +{ + *aServerIID = new nsIID(NS_GET_IID(nsINoIncomingServer)); + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetRequiresUsername(bool *aRequiresUsername) +{ + NS_ENSURE_ARG_POINTER(aRequiresUsername); + *aRequiresUsername = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress) +{ + NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress); + *aPreflightPrettyNameWithEmailAddress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp) +{ + NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp); + *aCanLoginAtStartUp = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetCanDelete(bool *aCanDelete) +{ + NS_ENSURE_ARG_POINTER(aCanDelete); + *aCanDelete = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetCanDuplicate(bool *aCanDuplicate) +{ + NS_ENSURE_ARG_POINTER(aCanDuplicate); + *aCanDuplicate = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetCanGetMessages(bool *aCanGetMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetMessages); + *aCanGetMessages = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages); + *aCanGetIncomingMessages = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetDefaultDoBiff(bool *aDoBiff) +{ + NS_ENSURE_ARG_POINTER(aDoBiff); + // by default, don't do biff for "none" servers + *aDoBiff = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetDefaultServerPort(bool isSecure, int32_t *aDefaultPort) +{ + NS_ENSURE_ARG_POINTER(aDefaultPort); + *aDefaultPort = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetShowComposeMsgLink(bool *showComposeMsgLink) +{ + NS_ENSURE_ARG_POINTER(showComposeMsgLink); + *showComposeMsgLink = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNoneService::GetFoldersCreatedAsync(bool *aAsyncCreation) +{ + NS_ENSURE_ARG_POINTER(aAsyncCreation); + *aAsyncCreation = false; + return NS_OK; +} diff --git a/mailnews/local/src/nsNoneService.h b/mailnews/local/src/nsNoneService.h new file mode 100644 index 000000000..fe8495528 --- /dev/null +++ b/mailnews/local/src/nsNoneService.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsNoneService_h___ +#define nsNoneService_h___ + +#include "nscore.h" + +#include "nsIMsgProtocolInfo.h" +#include "nsINoneService.h" + +class nsNoneService : public nsIMsgProtocolInfo, public nsINoneService +{ +public: + + nsNoneService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPROTOCOLINFO + NS_DECL_NSINONESERVICE + +private: + virtual ~nsNoneService(); +}; + +#endif /* nsNoneService_h___ */ diff --git a/mailnews/local/src/nsParseMailbox.cpp b/mailnews/local/src/nsParseMailbox.cpp new file mode 100644 index 000000000..9d68e5cd1 --- /dev/null +++ b/mailnews/local/src/nsParseMailbox.cpp @@ -0,0 +1,2624 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsIURI.h" +#include "nsParseMailbox.h" +#include "nsIMsgHdr.h" +#include "nsIMsgDatabase.h" +#include "nsMsgMessageFlags.h" +#include "nsIDBFolderInfo.h" +#include "nsIInputStream.h" +#include "nsIFile.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgBaseCID.h" +#include "nsMsgDBCID.h" +#include "nsIMailboxUrl.h" +#include "nsNetUtil.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgFolder.h" +#include "nsIURL.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgFilterList.h" +#include "nsIMsgFilter.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsRDFCID.h" +#include "nsIRDFService.h" +#include "nsMsgI18N.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsMsgUtils.h" +#include "prprf.h" +#include "prmem.h" +#include "nsISeekableStream.h" +#include "nsIMimeHeaders.h" +#include "nsIMsgMdnGenerator.h" +#include "nsMsgSearchCore.h" +#include "nsMailHeaders.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgComposeParams.h" +#include "nsMsgCompCID.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDocShell.h" +#include "nsIMsgCompose.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIMsgComposeService.h" +#include "nsIMsgCopyService.h" +#include "nsICryptoHash.h" +#include "nsIStringBundle.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIMsgFilterCustomAction.h" +#include <ctype.h> +#include "nsIMsgPluggableStore.h" +#include "mozilla/Services.h" +#include "nsQueryObject.h" +#include "nsIOutputStream.h" +#include "mozilla/Attributes.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +/* the following macros actually implement addref, release and query interface for our component. */ +NS_IMPL_ISUPPORTS_INHERITED(nsMsgMailboxParser, + nsParseMailMessageState, + nsIStreamListener, + nsIRequestObserver) + +// Whenever data arrives from the connection, core netlib notifices the protocol by calling +// OnDataAvailable. We then read and process the incoming data from the input stream. +NS_IMETHODIMP nsMsgMailboxParser::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *aIStream, uint64_t sourceOffset, uint32_t aLength) +{ + // right now, this really just means turn around and process the url + nsresult rv = NS_OK; + nsCOMPtr<nsIURI> url = do_QueryInterface(ctxt, &rv); + if (NS_SUCCEEDED(rv)) + rv = ProcessMailboxInputStream(url, aIStream, aLength); + return rv; +} + +NS_IMETHODIMP nsMsgMailboxParser::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + m_startTime = PR_Now(); + + + // extract the appropriate event sinks from the url and initialize them in our protocol data + // the URL should be queried for a nsIMailboxURL. If it doesn't support a mailbox URL interface then + // we have an error. + nsresult rv = NS_OK; + + nsCOMPtr<nsIIOService> ioServ = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioServ, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIMailboxUrl> runningUrl = do_QueryInterface(ctxt, &rv); + + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(ctxt); + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + + if (NS_SUCCEEDED(rv) && runningUrl && folder) + { + url->GetStatusFeedback(getter_AddRefs(m_statusFeedback)); + + // okay, now fill in our event sinks...Note that each getter ref counts before + // it returns the interface to us...we'll release when we are done + + folder->GetName(m_folderName); + + nsCOMPtr<nsIFile> path; + folder->GetFilePath(getter_AddRefs(path)); + + if (path) + { + int64_t fileSize; + path->GetFileSize(&fileSize); + // the size of the mailbox file is our total base line for measuring progress + m_graph_progress_total = fileSize; + UpdateStatusText("buildingSummary"); + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + // Use OpenFolderDB to always open the db so that db's m_folder + // is set correctly. + rv = msgDBService->OpenFolderDB(folder, true, + getter_AddRefs(m_mailDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + rv = msgDBService->CreateNewDB(folder, + getter_AddRefs(m_mailDB)); + + if (m_mailDB) + m_mailDB->AddListener(this); + } + NS_ASSERTION(m_mailDB, "failed to open mail db parsing folder"); + + // try to get a backup message database + nsresult rvignore = folder->GetBackupMsgDatabase( + getter_AddRefs(m_backupMailDB)); + + // We'll accept failures and move on, as we're dealing with some + // sort of unknown problem to begin with. + if (NS_FAILED(rvignore)) + { + if (m_backupMailDB) + m_backupMailDB->RemoveListener(this); + m_backupMailDB = nullptr; + } + else if (m_backupMailDB) + { + m_backupMailDB->AddListener(this); + } + } + } + + // need to get the mailbox name out of the url and call SetMailboxName with it. + // then, we need to open the mail db for this parser. + return rv; +} + +// stop binding is a "notification" informing us that the stream associated with aURL is going away. +NS_IMETHODIMP nsMsgMailboxParser::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) +{ + DoneParsingFolder(aStatus); + // what can we do? we can close the stream? + m_urlInProgress = false; // don't close the connection...we may be re-using it. + + if (m_mailDB) + m_mailDB->RemoveListener(this); + // and we want to mark ourselves for deletion or some how inform our protocol manager that we are + // available for another url if there is one.... + + ReleaseFolderLock(); + // be sure to clear any status text and progress info.. + m_graph_progress_received = 0; + UpdateProgressPercent(); + UpdateStatusText("localStatusDocumentDone"); + + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, + bool aPreChange, uint32_t *aStatus, nsIDBChangeListener * aInstigator) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, + uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, + nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrAdded(nsIMsgDBHdr *aHdrAdded, + nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnParentChanged(nsMsgKey aKeyChanged, + nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + if (m_backupMailDB && m_backupMailDB == instigator) + { + m_backupMailDB->RemoveListener(this); + m_backupMailDB = nullptr; + } + else if (m_mailDB) + { + m_mailDB->RemoveListener(this); + m_mailDB = nullptr; + m_newMsgHdr = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::OnEvent(nsIMsgDatabase *aDB, const char *aEvent) +{ + return NS_OK; +} + +/* void OnReadChanged (in nsIDBChangeListener instigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnReadChanged(nsIDBChangeListener *instigator) +{ + return NS_OK; +} + +/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnJunkScoreChanged(nsIDBChangeListener *instigator) +{ + return NS_OK; +} + +nsMsgMailboxParser::nsMsgMailboxParser() : nsMsgLineBuffer(nullptr, false) +{ + Init(); +} + +nsMsgMailboxParser::nsMsgMailboxParser(nsIMsgFolder *aFolder) : nsMsgLineBuffer(nullptr, false) +{ + m_folder = do_GetWeakReference(aFolder); +} + +nsMsgMailboxParser::~nsMsgMailboxParser() +{ + ReleaseFolderLock(); +} + +nsresult nsMsgMailboxParser::Init() +{ + m_obuffer = nullptr; + m_obuffer_size = 0; + m_graph_progress_total = 0; + m_graph_progress_received = 0; + return AcquireFolderLock(); +} + +void nsMsgMailboxParser::UpdateStatusText (const char* stringName) +{ + if (m_statusFeedback) + { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return; + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + return; + nsString finalString; + const char16_t * stringArray[] = { m_folderName.get() }; + rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(stringName).get(), + stringArray, 1, getter_Copies(finalString)); + m_statusFeedback->ShowStatusString(finalString); + } +} + +void nsMsgMailboxParser::UpdateProgressPercent () +{ + if (m_statusFeedback && m_graph_progress_total != 0) + { + // prevent overflow by dividing both by 100 + int64_t progressTotal = m_graph_progress_total / 100; + int64_t progressReceived = m_graph_progress_received / 100; + if (progressTotal > 0) + m_statusFeedback->ShowProgress((100 *(progressReceived)) / progressTotal); + } +} + +nsresult nsMsgMailboxParser::ProcessMailboxInputStream(nsIURI* aURL, nsIInputStream *aIStream, uint32_t aLength) +{ + nsresult ret = NS_OK; + + uint32_t bytesRead = 0; + + if (NS_SUCCEEDED(m_inputStream.GrowBuffer(aLength))) + { + // OK, this sucks, but we're going to have to copy into our + // own byte buffer, and then pass that to the line buffering code, + // which means a couple buffer copies. + ret = aIStream->Read(m_inputStream.GetBuffer(), aLength, &bytesRead); + if (NS_SUCCEEDED(ret)) + ret = BufferInput(m_inputStream.GetBuffer(), bytesRead); + } + if (m_graph_progress_total > 0) + { + if (NS_SUCCEEDED(ret)) + m_graph_progress_received += bytesRead; + } + return (ret); +} + +void nsMsgMailboxParser::DoneParsingFolder(nsresult status) +{ + /* End of file. Flush out any partial line remaining in the buffer. */ + FlushLastLine(); + PublishMsgHeader(nullptr); + + // only mark the db valid if we've succeeded. + if (NS_SUCCEEDED(status) && m_mailDB) // finished parsing, so flush db folder info + UpdateDBFolderInfo(); + else if (m_mailDB) + m_mailDB->SetSummaryValid(false); + + // remove the backup database + if (m_backupMailDB) + { + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + if (folder) + folder->RemoveBackupMsgDatabase(); + m_backupMailDB = nullptr; + } + + // if (m_folder != nullptr) + // m_folder->SummaryChanged(); + FreeBuffers(); +} + +void nsMsgMailboxParser::FreeBuffers() +{ + /* We're done reading the folder - we don't need these things + any more. */ + PR_FREEIF (m_obuffer); + m_obuffer_size = 0; +} + +void nsMsgMailboxParser::UpdateDBFolderInfo() +{ + UpdateDBFolderInfo(m_mailDB); +} + +// update folder info in db so we know not to reparse. +void nsMsgMailboxParser::UpdateDBFolderInfo(nsIMsgDatabase *mailDB) +{ + mailDB->SetSummaryValid(true); +} + +// Tell the world about the message header (add to db, and view, if any) +int32_t nsMsgMailboxParser::PublishMsgHeader(nsIMsgWindow *msgWindow) +{ + FinishHeader(); + if (m_newMsgHdr) + { + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_envelope_pos); + m_newMsgHdr->SetStringProperty("storeToken", storeToken); + + uint32_t flags; + (void)m_newMsgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Expunged) + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + uint32_t size; + (void)m_newMsgHdr->GetMessageSize(&size); + folderInfo->ChangeExpungedBytes(size); + m_newMsgHdr = nullptr; + } + else if (m_mailDB) + { + // add hdr but don't notify - shouldn't be requiring notifications + // during summary file rebuilding + m_mailDB->AddNewHdrToDB(m_newMsgHdr, false); + m_newMsgHdr = nullptr; + } + else + NS_ASSERTION(false, "no database while parsing local folder"); // should have a DB, no? + } + else if (m_mailDB) + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (folderInfo) + folderInfo->ChangeExpungedBytes(m_position - m_envelope_pos); + } + return 0; +} + +void nsMsgMailboxParser::AbortNewHeader() +{ + if (m_newMsgHdr && m_mailDB) + m_newMsgHdr = nullptr; +} + +void nsMsgMailboxParser::OnNewMessage(nsIMsgWindow *msgWindow) +{ + PublishMsgHeader(msgWindow); + Clear(); +} + +nsresult nsMsgMailboxParser::HandleLine(const char *line, uint32_t lineLength) +{ + /* If this is the very first line of a non-empty folder, make sure it's an envelope */ + if (m_graph_progress_received == 0) + { + /* This is the first block from the file. Check to see if this + looks like a mail file. */ + const char *s = line; + const char *end = s + lineLength; + while (s < end && IS_SPACE(*s)) + s++; + if ((end - s) < 20 || !IsEnvelopeLine(s, end - s)) + { +// char buf[500]; +// PR_snprintf (buf, sizeof(buf), +// XP_GetString(MK_MSG_NON_MAIL_FILE_READ_QUESTION), +// folder_name); +// else if (!FE_Confirm (m_context, buf)) +// return NS_MSG_NOT_A_MAIL_FOLDER; /* #### NOT_A_MAIL_FILE */ + } + } +// m_graph_progress_received += lineLength; + + // mailbox parser needs to do special stuff when it finds an envelope + // after parsing a message body. So do that. + if (line[0] == 'F' && IsEnvelopeLine(line, lineLength)) + { + // **** This used to be + // PR_ASSERT (m_parseMsgState->m_state == nsMailboxParseBodyState); + // **** I am not sure this is a right thing to do. This happens when + // going online, downloading a message while playing back append + // draft/template offline operation. We are mixing + // nsMailboxParseBodyState && + // nsMailboxParseHeadersState. David I need your help here too. **** jt + + NS_ASSERTION (m_state == nsIMsgParseMailMsgState::ParseBodyState || + m_state == nsIMsgParseMailMsgState::ParseHeadersState, "invalid parse state"); /* else folder corrupted */ + OnNewMessage(nullptr); + nsresult rv = StartNewEnvelope(line, lineLength); + NS_ASSERTION(NS_SUCCEEDED(rv), " error starting envelope parsing mailbox"); + // at the start of each new message, update the progress bar + UpdateProgressPercent(); + return rv; + } + + // otherwise, the message parser can handle it completely. + if (m_mailDB != nullptr) // if no DB, do we need to parse at all? + return ParseFolderLine(line, lineLength); + + return NS_ERROR_NULL_POINTER; // need to error out if we don't have a db. +} + +void +nsMsgMailboxParser::ReleaseFolderLock() +{ + nsresult result; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + if (!folder) + return; + bool haveSemaphore; + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this)); + result = folder->TestSemaphore(supports, &haveSemaphore); + if (NS_SUCCEEDED(result) && haveSemaphore) + (void) folder->ReleaseSemaphore(supports); +} + +nsresult +nsMsgMailboxParser::AcquireFolderLock() +{ + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + if (!folder) + return NS_ERROR_NULL_POINTER; + nsCOMPtr<nsISupports> supports = do_QueryObject(this); + return folder->AcquireSemaphore(supports); +} + +NS_IMPL_ISUPPORTS(nsParseMailMessageState, nsIMsgParseMailMsgState, nsIDBChangeListener) + +nsParseMailMessageState::nsParseMailMessageState() +{ + m_position = 0; + m_new_key = nsMsgKey_None; + m_IgnoreXMozillaStatus = false; + m_state = nsIMsgParseMailMsgState::ParseBodyState; + + // setup handling of custom db headers, headers that are added to .msf files + // as properties of the nsMsgHdr objects, controlled by the + // pref mailnews.customDBHeaders, a space-delimited list of headers. + // E.g., if mailnews.customDBHeaders is "X-Spam-Score", and we're parsing + // a mail message with the X-Spam-Score header, we'll set the + // "x-spam-score" property of nsMsgHdr to the value of the header. + m_customDBHeaderValues = nullptr; + nsCString customDBHeaders; // not shown in search UI + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + { + pPrefBranch->GetCharPref("mailnews.customDBHeaders", getter_Copies(customDBHeaders)); + ToLowerCase(customDBHeaders); + if (customDBHeaders.Find("content-base") == -1) + customDBHeaders.Insert(NS_LITERAL_CSTRING("content-base "), 0); + ParseString(customDBHeaders, ' ', m_customDBHeaders); + + // now add customHeaders + nsCString customHeadersString; // shown in search UI + nsTArray<nsCString> customHeadersArray; + pPrefBranch->GetCharPref("mailnews.customHeaders", getter_Copies(customHeadersString)); + ToLowerCase(customHeadersString); + customHeadersString.StripWhitespace(); + ParseString(customHeadersString, ':', customHeadersArray); + for (uint32_t i = 0; i < customHeadersArray.Length(); i++) + { + if (!m_customDBHeaders.Contains(customHeadersArray[i])) + m_customDBHeaders.AppendElement(customHeadersArray[i]); + } + + if (m_customDBHeaders.Length()) + { + m_customDBHeaderValues = new struct message_header [m_customDBHeaders.Length()]; + if (!m_customDBHeaderValues) + m_customDBHeaders.Clear(); + } + } + Clear(); +} + +nsParseMailMessageState::~nsParseMailMessageState() +{ + ClearAggregateHeader (m_toList); + ClearAggregateHeader (m_ccList); + delete [] m_customDBHeaderValues; +} + +void nsParseMailMessageState::Init(uint64_t fileposition) +{ + m_state = nsIMsgParseMailMsgState::ParseBodyState; + m_position = fileposition; + m_newMsgHdr = nullptr; +} + +NS_IMETHODIMP nsParseMailMessageState::Clear() +{ + m_message_id.length = 0; + m_references.length = 0; + m_date.length = 0; + m_delivery_date.length = 0; + m_from.length = 0; + m_sender.length = 0; + m_newsgroups.length = 0; + m_subject.length = 0; + m_status.length = 0; + m_mozstatus.length = 0; + m_mozstatus2.length = 0; + m_envelope_from.length = 0; + m_envelope_date.length = 0; + m_priority.length = 0; + m_keywords.length = 0; + m_mdn_dnt.length = 0; + m_return_path.length = 0; + m_account_key.length = 0; + m_in_reply_to.length = 0; + m_replyTo.length = 0; + m_content_type.length = 0; + m_mdn_original_recipient.length = 0; + m_bccList.length = 0; + m_body_lines = 0; + m_newMsgHdr = nullptr; + m_envelope_pos = 0; + m_new_key = nsMsgKey_None; + ClearAggregateHeader (m_toList); + ClearAggregateHeader (m_ccList); + m_headers.ResetWritePos(); + m_envelope.ResetWritePos(); + m_receivedTime = 0; + m_receivedValue.Truncate(); + for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) + m_customDBHeaderValues[i].length = 0; + + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetState(nsMailboxParseState aState) +{ + m_state = aState; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::GetState(nsMailboxParseState *aState) +{ + if (!aState) + return NS_ERROR_NULL_POINTER; + + *aState = m_state; + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::GetEnvelopePos(uint64_t *aEnvelopePos) +{ + NS_ENSURE_ARG_POINTER(aEnvelopePos); + + *aEnvelopePos = m_envelope_pos; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetEnvelopePos(uint64_t aEnvelopePos) +{ + m_envelope_pos = aEnvelopePos; + m_position = m_envelope_pos; + m_headerstartpos = m_position; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::GetNewMsgHdr(nsIMsgDBHdr ** aMsgHeader) +{ + NS_ENSURE_ARG_POINTER(aMsgHeader); + NS_IF_ADDREF(*aMsgHeader = m_newMsgHdr); + return m_newMsgHdr ? NS_OK : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsParseMailMessageState::SetNewMsgHdr(nsIMsgDBHdr *aMsgHeader) +{ + m_newMsgHdr = aMsgHeader; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::ParseAFolderLine(const char *line, uint32_t lineLength) +{ + ParseFolderLine(line, lineLength); + return NS_OK; +} + +nsresult nsParseMailMessageState::ParseFolderLine(const char *line, uint32_t lineLength) +{ + nsresult rv; + + if (m_state == nsIMsgParseMailMsgState::ParseHeadersState) + { + if (EMPTY_MESSAGE_LINE(line)) + { + /* End of headers. Now parse them. */ + rv = ParseHeaders(); + NS_ASSERTION(NS_SUCCEEDED(rv), "error parsing headers parsing mailbox"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = FinalizeHeaders(); + NS_ASSERTION(NS_SUCCEEDED(rv), "error finalizing headers parsing mailbox"); + NS_ENSURE_SUCCESS(rv, rv); + + m_state = nsIMsgParseMailMsgState::ParseBodyState; + } + else + { + /* Otherwise, this line belongs to a header. So append it to the + header data, and stay in MBOX `MIME_PARSE_HEADERS' state. + */ + m_headers.AppendBuffer(line, lineLength); + } + } + else if ( m_state == nsIMsgParseMailMsgState::ParseBodyState) + { + m_body_lines++; + } + + m_position += lineLength; + + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetMailDB(nsIMsgDatabase *mailDB) +{ + m_mailDB = mailDB; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetBackupMailDB(nsIMsgDatabase *aBackupMailDB) +{ + m_backupMailDB = aBackupMailDB; + if (m_backupMailDB) + m_backupMailDB->AddListener(this); + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetNewKey(nsMsgKey aKey) +{ + m_new_key = aKey; + return NS_OK; +} + +/* #define STRICT_ENVELOPE */ + +bool +nsParseMailMessageState::IsEnvelopeLine(const char *buf, int32_t buf_size) +{ +#ifdef STRICT_ENVELOPE + /* The required format is + From jwz Fri Jul 1 09:13:09 1994 + But we should also allow at least: + From jwz Fri, Jul 01 09:13:09 1994 + From jwz Fri Jul 1 09:13:09 1994 PST + From jwz Fri Jul 1 09:13:09 1994 (+0700) + + We can't easily call XP_ParseTimeString() because the string is not + null terminated (ok, we could copy it after a quick check...) but + XP_ParseTimeString() may be too lenient for our purposes. + + DANGER!! The released version of 2.0b1 was (on some systems, + some Unix, some NT, possibly others) writing out envelope lines + like "From - 10/13/95 11:22:33" which STRICT_ENVELOPE will reject! + */ + const char *date, *end; + + if (buf_size < 29) return false; + if (*buf != 'F') return false; + if (strncmp(buf, "From ", 5)) return false; + + end = buf + buf_size; + date = buf + 5; + + /* Skip horizontal whitespace between "From " and user name. */ + while ((*date == ' ' || *date == '\t') && date < end) + date++; + + /* If at the end, it doesn't match. */ + if (IS_SPACE(*date) || date == end) + return false; + + /* Skip over user name. */ + while (!IS_SPACE(*date) && date < end) + date++; + + /* Skip horizontal whitespace between user name and date. */ + while ((*date == ' ' || *date == '\t') && date < end) + date++; + + /* Don't want this to be localized. */ +# define TMP_ISALPHA(x) (((x) >= 'A' && (x) <= 'Z') || \ + ((x) >= 'a' && (x) <= 'z')) + + /* take off day-of-the-week. */ + if (date >= end - 3) + return false; + if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2])) + return false; + date += 3; + /* Skip horizontal whitespace (and commas) between dotw and month. */ + if (*date != ' ' && *date != '\t' && *date != ',') + return false; + while ((*date == ' ' || *date == '\t' || *date == ',') && date < end) + date++; + + /* take off month. */ + if (date >= end - 3) + return false; + if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2])) + return false; + date += 3; + /* Skip horizontal whitespace between month and dotm. */ + if (date == end || (*date != ' ' && *date != '\t')) + return false; + while ((*date == ' ' || *date == '\t') && date < end) + date++; + + /* Skip over digits and whitespace. */ + while (((*date >= '0' && *date <= '9') || *date == ' ' || *date == '\t') && + date < end) + date++; + /* Next character should be a colon. */ + if (date >= end || *date != ':') + return false; + + /* Ok, that ought to be enough... */ + +# undef TMP_ISALPHA + +#else /* !STRICT_ENVELOPE */ + + if (buf_size < 5) return false; + if (*buf != 'F') return false; + if (strncmp(buf, "From ", 5)) return false; + +#endif /* !STRICT_ENVELOPE */ + + return true; +} + +// We've found the start of the next message, so finish this one off. +NS_IMETHODIMP nsParseMailMessageState::FinishHeader() +{ + if (m_newMsgHdr) + { + m_newMsgHdr->SetMessageOffset(m_envelope_pos); + m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos); + m_newMsgHdr->SetLineCount(m_body_lines); + } + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::GetAllHeaders(char ** pHeaders, int32_t *pHeadersSize) +{ + if (!pHeaders || !pHeadersSize) + return NS_ERROR_NULL_POINTER; + *pHeaders = m_headers.GetBuffer(); + *pHeadersSize = m_headers.GetBufferPos(); + return NS_OK; +} + +// generate headers as a string, with CRLF between the headers +NS_IMETHODIMP nsParseMailMessageState::GetHeaders(char ** pHeaders) +{ + NS_ENSURE_ARG_POINTER(pHeaders); + nsCString crlfHeaders; + char *curHeader = m_headers.GetBuffer(); + for (uint32_t headerPos = 0; headerPos < m_headers.GetBufferPos();) + { + crlfHeaders.Append(curHeader); + crlfHeaders.Append(CRLF); + int32_t headerLen = strlen(curHeader); + curHeader += headerLen + 1; + headerPos += headerLen + 1; + } + *pHeaders = ToNewCString(crlfHeaders); + return NS_OK; +} + +struct message_header *nsParseMailMessageState::GetNextHeaderInAggregate (nsTArray<struct message_header*> &list) +{ + // When parsing a message with multiple To or CC header lines, we're storing each line in a + // list, where the list represents the "aggregate" total of all the header. Here we get a new + // line for the list + + struct message_header *header = (struct message_header*) PR_Calloc (1, sizeof(struct message_header)); + list.AppendElement (header); + return header; +} + +void nsParseMailMessageState::GetAggregateHeader (nsTArray<struct message_header*> &list, struct message_header *outHeader) +{ + // When parsing a message with multiple To or CC header lines, we're storing each line in a + // list, where the list represents the "aggregate" total of all the header. Here we combine + // all the lines together, as though they were really all found on the same line + + struct message_header *header = nullptr; + int length = 0; + size_t i; + + // Count up the bytes required to allocate the aggregated header + for (i = 0; i < list.Length(); i++) + { + header = list.ElementAt(i); + length += (header->length + 1); //+ for "," + } + + if (length > 0) + { + char *value = (char*) PR_CALLOC (length + 1); //+1 for null term + if (value) + { + // Catenate all the To lines together, separated by commas + value[0] = '\0'; + size_t size = list.Length(); + for (i = 0; i < size; i++) + { + header = list.ElementAt(i); + PL_strncat (value, header->value, header->length); + if (i + 1 < size) + PL_strcat (value, ","); + } + outHeader->length = length; + outHeader->value = value; + } + } + else + { + outHeader->length = 0; + outHeader->value = nullptr; + } +} + +void nsParseMailMessageState::ClearAggregateHeader (nsTArray<struct message_header*> &list) +{ + // Reset the aggregate headers. Free only the message_header struct since + // we don't own the value pointer + + for (size_t i = 0; i < list.Length(); i++) + PR_Free (list.ElementAt(i)); + list.Clear(); +} + +// We've found a new envelope to parse. +nsresult nsParseMailMessageState::StartNewEnvelope(const char *line, uint32_t lineLength) +{ + m_envelope_pos = m_position; + m_state = nsIMsgParseMailMsgState::ParseHeadersState; + m_position += lineLength; + m_headerstartpos = m_position; + return ParseEnvelope (line, lineLength); +} + +/* largely lifted from mimehtml.c, which does similar parsing, sigh... +*/ +nsresult nsParseMailMessageState::ParseHeaders () +{ + char *buf = m_headers.GetBuffer(); + uint32_t buf_length = m_headers.GetBufferPos(); + if (buf_length == 0) + { + // No header of an expected type is present. Consider this a successful + // parse so email still shows on summary and can be accessed and deleted. + return NS_OK; + } + char *buf_end = buf + buf_length; + if (!(buf_length > 1 && (buf[buf_length - 1] == '\r' || + buf[buf_length - 1] == '\n'))) + { + NS_WARNING("Header text should always end in a newline"); + return NS_ERROR_UNEXPECTED; + } + while (buf < buf_end) + { + char *colon = PL_strnchr(buf, ':', buf_end - buf); + char *end; + char *value = 0; + struct message_header *header = 0; + struct message_header receivedBy; + + if (!colon) + break; + + end = colon; + + switch (buf [0]) + { + case 'B': case 'b': + if (!PL_strncasecmp ("BCC", buf, end - buf)) + header = &m_bccList; + break; + case 'C': case 'c': + if (!PL_strncasecmp ("CC", buf, end - buf)) + header = GetNextHeaderInAggregate(m_ccList); + else if (!PL_strncasecmp ("Content-Type", buf, end - buf)) + header = &m_content_type; + break; + case 'D': case 'd': + if (!PL_strncasecmp ("Date", buf, end - buf)) + header = &m_date; + else if (!PL_strncasecmp("Disposition-Notification-To", buf, end - buf)) + header = &m_mdn_dnt; + else if (!PL_strncasecmp("Delivery-date", buf, end - buf)) + header = &m_delivery_date; + break; + case 'F': case 'f': + if (!PL_strncasecmp ("From", buf, end - buf)) + header = &m_from; + break; + case 'I' : case 'i': + if (!PL_strncasecmp ("In-Reply-To", buf, end - buf)) + header = &m_in_reply_to; + break; + case 'M': case 'm': + if (!PL_strncasecmp ("Message-ID", buf, end - buf)) + header = &m_message_id; + break; + case 'N': case 'n': + if (!PL_strncasecmp ("Newsgroups", buf, end - buf)) + header = &m_newsgroups; + break; + case 'O': case 'o': + if (!PL_strncasecmp ("Original-Recipient", buf, end - buf)) + header = &m_mdn_original_recipient; + break; + case 'R': case 'r': + if (!PL_strncasecmp ("References", buf, end - buf)) + header = &m_references; + else if (!PL_strncasecmp ("Return-Path", buf, end - buf)) + header = &m_return_path; + // treat conventional Return-Receipt-To as MDN + // Disposition-Notification-To + else if (!PL_strncasecmp ("Return-Receipt-To", buf, end - buf)) + header = &m_mdn_dnt; + else if (!PL_strncasecmp("Reply-To", buf, end - buf)) + header = &m_replyTo; + else if (!PL_strncasecmp("Received", buf, end - buf)) + { + header = &receivedBy; + header->length = 0; + } + break; + case 'S': case 's': + if (!PL_strncasecmp ("Subject", buf, end - buf) && !m_subject.length) + header = &m_subject; + else if (!PL_strncasecmp ("Sender", buf, end - buf)) + header = &m_sender; + else if (!PL_strncasecmp ("Status", buf, end - buf)) + header = &m_status; + break; + case 'T': case 't': + if (!PL_strncasecmp ("To", buf, end - buf)) + header = GetNextHeaderInAggregate(m_toList); + break; + case 'X': + if (X_MOZILLA_STATUS2_LEN == end - buf && + !PL_strncasecmp(X_MOZILLA_STATUS2, buf, end - buf) && + !m_IgnoreXMozillaStatus && !m_mozstatus2.length) + header = &m_mozstatus2; + else if ( X_MOZILLA_STATUS_LEN == end - buf && + !PL_strncasecmp(X_MOZILLA_STATUS, buf, end - buf) && !m_IgnoreXMozillaStatus + && !m_mozstatus.length) + header = &m_mozstatus; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf, end - buf) + && !m_account_key.length) + header = &m_account_key; + // we could very well care what the priority header was when we + // remember its value. If so, need to remember it here. Also, + // different priority headers can appear in the same message, + // but we only rememeber the last one that we see. + else if (!PL_strncasecmp("X-Priority", buf, end - buf) + || !PL_strncasecmp("Priority", buf, end - buf)) + header = &m_priority; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf) + && !m_keywords.length) + header = &m_keywords; + break; + } + if (!header && m_customDBHeaders.Length()) + { +#ifdef MOZILLA_INTERNAL_API + nsDependentCSubstring headerStr(buf, end); +#else + nsDependentCSubstring headerStr(buf, end - buf); +#endif + + ToLowerCase(headerStr); + size_t customHeaderIndex = m_customDBHeaders.IndexOf(headerStr); + if (customHeaderIndex != m_customDBHeaders.NoIndex) + header = & m_customDBHeaderValues[customHeaderIndex]; + } + + buf = colon + 1; + uint32_t writeOffset = 0; // number of characters replaced with a folded space + +SEARCH_NEWLINE: + // move past any non terminating characters, rewriting them if folding white space + // exists + while (buf < buf_end && *buf != '\r' && *buf != '\n') + { + if (writeOffset) + *(buf - writeOffset) = *buf; + buf++; + } + + /* If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. */ + if ((buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') && + (buf[2] == ' ' || buf[2] == '\t')) || + /* If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate + the header either. */ + (buf + 1 < buf_end && (buf[0] == '\r' || buf[0] == '\n') && + (buf[1] == ' ' || buf[1] == '\t'))) + { + // locate the proper location for a folded space by eliminating any + // leading spaces before the end-of-line character + char* foldedSpace = buf; + while (*(foldedSpace - 1) == ' ' || *(foldedSpace - 1) == '\t') + foldedSpace--; + + // put a single folded space character + *(foldedSpace - writeOffset) = ' '; + writeOffset += (buf - foldedSpace); + buf++; + + // eliminate any additional white space + while (buf < buf_end && + (*buf == '\n' || *buf == '\r' || *buf == ' ' || *buf == '\t')) + { + buf++; + writeOffset++; + } + + // If we get here, the message headers ended in an empty line, like: + // To: blah blah blah<CR><LF> <CR><LF>[end of buffer]. The code below + // requires buf to land on a newline to properly null-terminate the + // string, so back up a tad so that it is pointing to one. + if (buf == buf_end) + { + --buf; + MOZ_ASSERT(*buf == '\n' || *buf == '\r', + "Header text should always end in a newline."); + } + goto SEARCH_NEWLINE; + } + + if (header) + { + value = colon + 1; + // eliminate trailing blanks after the colon + while (value < (buf - writeOffset) && (*value == ' ' || *value == '\t')) + value++; + + header->value = value; + header->length = buf - header->value - writeOffset; + if (header->length < 0) + header->length = 0; + } + if (*buf == '\r' || *buf == '\n') + { + char *last = buf - writeOffset; + char *saveBuf = buf; + if (*buf == '\r' && buf + 1 < buf_end && buf[1] == '\n') + buf++; + buf++; + // null terminate the left-over slop so we don't confuse msg filters. + *saveBuf = 0; + *last = 0; /* short-circuit const, and null-terminate header. */ + } + + if (header) + { + /* More const short-circuitry... */ + /* strip trailing whitespace */ + while (header->length > 0 && + IS_SPACE (header->value [header->length - 1])) + ((char *) header->value) [--header->length] = 0; + if (header == &receivedBy) + { + if (m_receivedTime == 0) + { + // parse Received: header for date. + // We trust the first header as that is closest to recipient, + // and less likely to be spoofed. + nsAutoCString receivedHdr(header->value, header->length); + int32_t lastSemicolon = receivedHdr.RFindChar(';'); + if (lastSemicolon != -1) + { + nsAutoCString receivedDate; + receivedDate = Substring(receivedHdr, lastSemicolon + 1); + receivedDate.Trim(" \t\b\r\n"); + PRTime resultTime; + if (PR_ParseTimeString (receivedDate.get(), false, &resultTime) == PR_SUCCESS) + m_receivedTime = resultTime; + else + NS_WARNING("PR_ParseTimeString failed in ParseHeaders()."); + } + } + // Someone might want the received header saved. + if (m_customDBHeaders.Length()) + { + if (m_customDBHeaders.Contains(NS_LITERAL_CSTRING("received"))) + { + if (!m_receivedValue.IsEmpty()) + m_receivedValue.Append(' '); + m_receivedValue.Append(header->value, header->length); + } + } + } + + MOZ_ASSERT(header->value[header->length] == 0, + "Non-null-terminated strings cause very, very bad problems"); + } + } + return NS_OK; +} + +nsresult nsParseMailMessageState::ParseEnvelope (const char *line, uint32_t line_size) +{ + const char *end; + char *s; + + m_envelope.AppendBuffer(line, line_size); + end = m_envelope.GetBuffer() + line_size; + s = m_envelope.GetBuffer() + 5; + + while (s < end && IS_SPACE (*s)) + s++; + m_envelope_from.value = s; + while (s < end && !IS_SPACE (*s)) + s++; + m_envelope_from.length = s - m_envelope_from.value; + + while (s < end && IS_SPACE (*s)) + s++; + m_envelope_date.value = s; + m_envelope_date.length = (uint16_t) (line_size - (s - m_envelope.GetBuffer())); + + while (m_envelope_date.length > 0 && + IS_SPACE (m_envelope_date.value [m_envelope_date.length - 1])) + m_envelope_date.length--; + + /* #### short-circuit const */ + ((char *) m_envelope_from.value) [m_envelope_from.length] = 0; + ((char *) m_envelope_date.value) [m_envelope_date.length] = 0; + + return NS_OK; +} + +nsresult nsParseMailMessageState::InternSubject (struct message_header *header) +{ + if (!header || header->length == 0) + { + m_newMsgHdr->SetSubject(""); + return NS_OK; + } + + const char *key = header->value; + + uint32_t flags; + (void)m_newMsgHdr->GetFlags(&flags); + /* strip "Re: " */ + /** + We trust the X-Mozilla-Status line to be the smartest in almost + all things. One exception, however, is the HAS_RE flag. Since + we just parsed the subject header anyway, we expect that parsing + to be smartest. (After all, what if someone just went in and + edited the subject line by hand?) + */ + nsCString modifiedSubject; + if (NS_MsgStripRE(nsDependentCString(key), modifiedSubject)) + flags |= nsMsgMessageFlags::HasRe; + else + flags &= ~nsMsgMessageFlags::HasRe; + m_newMsgHdr->SetFlags(flags); // this *does not* update the mozilla-status header in the local folder + + // Condense the subject text into as few MIME-2 encoded words as possible. + m_newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? key : modifiedSubject.get()); + + return NS_OK; +} + +// we've reached the end of the envelope, and need to turn all our accumulated message_headers +// into a single nsIMsgDBHdr to store in a database. +nsresult nsParseMailMessageState::FinalizeHeaders() +{ + nsresult rv; + struct message_header *sender; + struct message_header *recipient; + struct message_header *subject; + struct message_header *id; + struct message_header *inReplyTo; + struct message_header *replyTo; + struct message_header *references; + struct message_header *date; + struct message_header *deliveryDate; + struct message_header *statush; + struct message_header *mozstatus; + struct message_header *mozstatus2; + struct message_header *priority; + struct message_header *keywords; + struct message_header *account_key; + struct message_header *ccList; + struct message_header *bccList; + struct message_header *mdn_dnt; + struct message_header md5_header; + struct message_header *content_type; + char md5_data [50]; + + uint32_t flags = 0; + uint32_t delta = 0; + nsMsgPriorityValue priorityFlags = nsMsgPriority::notSet; + uint32_t labelFlags = 0; + + if (!m_mailDB) // if we don't have a valid db, skip the header. + return NS_OK; + + struct message_header to; + GetAggregateHeader (m_toList, &to); + struct message_header cc; + GetAggregateHeader (m_ccList, &cc); + // we don't aggregate bcc, as we only generate it locally, + // and we don't use multiple lines + + sender = (m_from.length ? &m_from : + m_sender.length ? &m_sender : + m_envelope_from.length ? &m_envelope_from : + 0); + recipient = (to.length ? &to : + cc.length ? &cc : + m_newsgroups.length ? &m_newsgroups : + 0); + ccList = (cc.length ? &cc : 0); + bccList = (m_bccList.length ? &m_bccList : 0); + subject = (m_subject.length ? &m_subject : 0); + id = (m_message_id.length ? &m_message_id : 0); + references = (m_references.length ? &m_references : 0); + statush = (m_status.length ? &m_status : 0); + mozstatus = (m_mozstatus.length ? &m_mozstatus : 0); + mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0); + date = (m_date.length ? &m_date : + m_envelope_date.length ? &m_envelope_date : + 0); + deliveryDate = (m_delivery_date.length ? &m_delivery_date : 0); + priority = (m_priority.length ? &m_priority : 0); + keywords = (m_keywords.length ? &m_keywords : 0); + mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0); + inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0); + replyTo = (m_replyTo.length ? &m_replyTo : 0); + content_type = (m_content_type.length ? &m_content_type : 0); + account_key = (m_account_key.length ? &m_account_key :0); + + if (mozstatus) + { + if (mozstatus->length == 4) + { + NS_ASSERTION(MsgIsHex(mozstatus->value, 4), "Expected 4 hex digits for flags."); + flags = MsgUnhex(mozstatus->value, 4); + // strip off and remember priority bits. + flags &= ~nsMsgMessageFlags::RuntimeOnly; + priorityFlags = (nsMsgPriorityValue) ((flags & nsMsgMessageFlags::Priorities) >> 13); + flags &= ~nsMsgMessageFlags::Priorities; + } + delta = (m_headerstartpos + + (mozstatus->value - m_headers.GetBuffer()) - + (2 + X_MOZILLA_STATUS_LEN) /* 2 extra bytes for ": ". */ + ) - m_envelope_pos; + } + + if (mozstatus2) + { + uint32_t flags2 = 0; + sscanf(mozstatus2->value, " %x ", &flags2); + flags |= flags2; + } + + if (!(flags & nsMsgMessageFlags::Expunged)) // message was deleted, don't bother creating a hdr. + { + // We'll need the message id first to recover data from the backup database + nsAutoCString rawMsgId; + /* Take off <> around message ID. */ + if (id) + { + if (id->length > 0 && id->value[0] == '<') + id->length--, id->value++; + + NS_WARNING_ASSERTION(id->length > 0, "id->length failure in FinalizeHeaders()."); + + if (id->length > 0 && id->value[id->length - 1] == '>') + /* generate a new null-terminated string without the final > */ + rawMsgId.Assign(id->value, id->length - 1); + else + rawMsgId.Assign(id->value); + } + + /* + * Try to copy the data from the backup database, referencing the MessageID + * If that fails, just create a new header + */ + nsCOMPtr<nsIMsgDBHdr> oldHeader; + nsresult ret = NS_OK; + + if (m_backupMailDB && !rawMsgId.IsEmpty()) + ret = m_backupMailDB->GetMsgHdrForMessageID( + rawMsgId.get(), getter_AddRefs(oldHeader)); + + // m_new_key is set in nsImapMailFolder::ParseAdoptedHeaderLine to be + // the UID of the message, so that the key can get created as UID. That of + // course is extremely confusing, and we really need to clean that up. We + // really should not conflate the meaning of envelope position, key, and + // UID. + if (NS_SUCCEEDED(ret) && oldHeader) + ret = m_mailDB->CopyHdrFromExistingHdr(m_new_key, + oldHeader, false, getter_AddRefs(m_newMsgHdr)); + else if (!m_newMsgHdr) + { + // Should assert that this is not a local message + ret = m_mailDB->CreateNewHdr(m_new_key, getter_AddRefs(m_newMsgHdr)); + } + + if (NS_SUCCEEDED(ret) && m_newMsgHdr) + { + uint32_t origFlags; + (void)m_newMsgHdr->GetFlags(&origFlags); + if (origFlags & nsMsgMessageFlags::HasRe) + flags |= nsMsgMessageFlags::HasRe; + else + flags &= ~nsMsgMessageFlags::HasRe; + + flags &= ~nsMsgMessageFlags::Offline; // don't keep nsMsgMessageFlags::Offline for local msgs + if (mdn_dnt && !(origFlags & nsMsgMessageFlags::Read) && + !(origFlags & nsMsgMessageFlags::MDNReportSent) && + !(flags & nsMsgMessageFlags::MDNReportSent)) + flags |= nsMsgMessageFlags::MDNReportNeeded; + + m_newMsgHdr->SetFlags(flags); + if (priorityFlags != nsMsgPriority::notSet) + m_newMsgHdr->SetPriority(priorityFlags); + + // if we have a reply to header, and it's different from the from: header, + // set the "replyTo" attribute on the msg hdr. + if (replyTo && (!sender || replyTo->length != sender->length || strncmp(replyTo->value, sender->value, sender->length))) + m_newMsgHdr->SetStringProperty("replyTo", replyTo->value); + // convert the flag values (0xE000000) to label values (0-5) + if (mozstatus2) // only do this if we have a mozstatus2 header + { + labelFlags = ((flags & nsMsgMessageFlags::Labels) >> 25); + m_newMsgHdr->SetLabel(labelFlags); + } + if (delta < 0xffff) + { /* Only use if fits in 16 bits. */ + m_newMsgHdr->SetStatusOffset((uint16_t) delta); + if (!m_IgnoreXMozillaStatus) { // imap doesn't care about X-MozillaStatus + uint32_t offset; + (void)m_newMsgHdr->GetStatusOffset(&offset); + NS_ASSERTION(offset < 10000, "invalid status offset"); /* ### Debugging hack */ + } + } + if (sender) + m_newMsgHdr->SetAuthor(sender->value); + if (recipient == &m_newsgroups) + { + /* In the case where the recipient is a newsgroup, truncate the string + at the first comma. This is used only for presenting the thread list, + and newsgroup lines tend to be long and non-shared, and tend to bloat + the string table. So, by only showing the first newsgroup, we can + reduce memory and file usage at the expense of only showing the one + group in the summary list, and only being able to sort on the first + group rather than the whole list. It's worth it. */ + char * ch; + ch = PL_strchr(recipient->value, ','); + if (ch) + { + /* generate a new string that terminates before the , */ + nsAutoCString firstGroup; + firstGroup.Assign(recipient->value, ch - recipient->value); + m_newMsgHdr->SetRecipients(firstGroup.get()); + } + m_newMsgHdr->SetRecipients(recipient->value); + } + else if (recipient) + { + m_newMsgHdr->SetRecipients(recipient->value); + } + if (ccList) + { + m_newMsgHdr->SetCcList(ccList->value); + } + + if (bccList) + { + m_newMsgHdr->SetBccList(bccList->value); + } + + rv = InternSubject (subject); + if (NS_SUCCEEDED(rv)) + { + if (!id) + { + // what to do about this? we used to do a hash of all the headers... + nsAutoCString hash; + const char *md5_b64 = "dummy.message.id"; + nsresult rv; + nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + if (NS_SUCCEEDED(rv)) + { + if (NS_SUCCEEDED(hasher->Init(nsICryptoHash::MD5)) && + NS_SUCCEEDED(hasher->Update((const uint8_t*) m_headers.GetBuffer(), m_headers.GetSize())) && + NS_SUCCEEDED(hasher->Finish(true, hash))) + md5_b64 = hash.get(); + } + PR_snprintf (md5_data, sizeof(md5_data), "<md5:%s>", md5_b64); + md5_header.value = md5_data; + md5_header.length = strlen(md5_data); + id = &md5_header; + } + + if (!rawMsgId.IsEmpty()) + m_newMsgHdr->SetMessageId(rawMsgId.get()); + else + m_newMsgHdr->SetMessageId(id->value); + m_mailDB->UpdatePendingAttributes(m_newMsgHdr); + + if (!mozstatus && statush) + { + /* Parse a little bit of the Berkeley Mail status header. */ + for (const char *s = statush->value; *s; s++) { + uint32_t msgFlags = 0; + (void)m_newMsgHdr->GetFlags(&msgFlags); + switch (*s) + { + case 'R': case 'r': + m_newMsgHdr->SetFlags(msgFlags | nsMsgMessageFlags::Read); + break; + case 'D': case 'd': + /* msg->flags |= nsMsgMessageFlags::Expunged; ### Is this reasonable? */ + break; + case 'N': case 'n': + case 'U': case 'u': + m_newMsgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::Read); + break; + default: // Should check for corrupt file. + NS_ERROR("Corrupt file. Should not happen."); + break; + } + } + } + + if (account_key != nullptr) + m_newMsgHdr->SetAccountKey(account_key->value); + // use in-reply-to header as references, if there's no references header + if (references != nullptr) + m_newMsgHdr->SetReferences(references->value); + else if (inReplyTo != nullptr) + m_newMsgHdr->SetReferences(inReplyTo->value); + + // 'Received' should be as reliable an indicator of the receipt + // date+time as possible, whilst always giving something *from + // the message*. It won't use PR_Now() under any circumstance. + // Therefore, the fall-thru order for 'Received' is: + // Received: -> Delivery-date: -> date + // 'Date' uses: + // date -> PR_Now() + // + // date is: + // Date: -> m_envelope_date + + uint32_t rcvTimeSecs = 0; + if (date) + { // Date: + PRTime resultTime; + PRStatus timeStatus = PR_ParseTimeString (date->value, false, &resultTime); + if (PR_SUCCESS == timeStatus) + { + m_newMsgHdr->SetDate(resultTime); + PRTime2Seconds(resultTime, &rcvTimeSecs); + } + else + NS_WARNING("PR_ParseTimeString of date failed in FinalizeHeader()."); + } + else + { // PR_Now() + // If there was some problem parsing the Date header *AND* we + // couldn't get a valid envelope date, use now as the time. + // PR_ParseTimeString won't touch resultTime unless it succeeds. + // This doesn't affect local (POP3) messages, because we use the envelope + // date if there's no Date: header, but it will affect IMAP msgs + // w/o a Date: hdr or Received: headers. + PRTime resultTime = PR_Now(); + m_newMsgHdr->SetDate(resultTime); + } + if (m_receivedTime != 0) + { // Upgrade 'Received' to Received: ? + PRTime2Seconds(m_receivedTime, &rcvTimeSecs); + } + else if (deliveryDate) + { // Upgrade 'Received' to Delivery-date: ? + PRTime resultTime; + PRStatus timeStatus = PR_ParseTimeString (deliveryDate->value, false, &resultTime); + if (PR_SUCCESS == timeStatus) + PRTime2Seconds(resultTime, &rcvTimeSecs); + else // TODO/FIXME: We need to figure out what to do in this case! + NS_WARNING("PR_ParseTimeString of delivery date failed in FinalizeHeader()."); + } + m_newMsgHdr->SetUint32Property("dateReceived", rcvTimeSecs); + + if (priority) + m_newMsgHdr->SetPriorityString(priority->value); + else if (priorityFlags == nsMsgPriority::notSet) + m_newMsgHdr->SetPriority(nsMsgPriority::none); + if (keywords) + { + // When there are many keywords, some may not have been written + // to the message file, so add extra keywords from the backup + nsAutoCString oldKeywords; + m_newMsgHdr->GetStringProperty("keywords", getter_Copies(oldKeywords)); + nsTArray<nsCString> newKeywordArray, oldKeywordArray; + ParseString(Substring(keywords->value, keywords->value + keywords->length), ' ', newKeywordArray); + ParseString(oldKeywords, ' ', oldKeywordArray); + for (uint32_t i = 0; i < oldKeywordArray.Length(); i++) + if (!newKeywordArray.Contains(oldKeywordArray[i])) + newKeywordArray.AppendElement(oldKeywordArray[i]); + nsAutoCString newKeywords; + for (uint32_t i = 0; i < newKeywordArray.Length(); i++) + { + if (i) + newKeywords.Append(" "); + newKeywords.Append(newKeywordArray[i]); + } + m_newMsgHdr->SetStringProperty("keywords", newKeywords.get()); + } + for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) + { + if (m_customDBHeaderValues[i].length) + m_newMsgHdr->SetStringProperty(m_customDBHeaders[i].get(), m_customDBHeaderValues[i].value); + // The received header is accumulated separately + if (m_customDBHeaders[i].EqualsLiteral("received") && !m_receivedValue.IsEmpty()) + m_newMsgHdr->SetStringProperty("received", m_receivedValue.get()); + } + if (content_type) + { + char *substring = PL_strstr(content_type->value, "charset"); + if (substring) + { + char *charset = PL_strchr (substring, '='); + if (charset) + { + charset++; + /* strip leading whitespace and double-quote */ + while (*charset && (IS_SPACE (*charset) || '\"' == *charset)) + charset++; + /* strip trailing whitespace and double-quote */ + char *end = charset; + while (*end && !IS_SPACE (*end) && '\"' != *end && ';' != *end) + end++; + if (*charset) + { + if (*end != '\0') { + // if we're not at the very end of the line, we need + // to generate a new string without the trailing crud + nsAutoCString rawCharSet; + rawCharSet.Assign(charset, end - charset); + m_newMsgHdr->SetCharset(rawCharSet.get()); + } else { + m_newMsgHdr->SetCharset(charset); + } + } + } + } + substring = PL_strcasestr(content_type->value, "multipart/mixed"); + if (substring) + { + uint32_t newFlags; + m_newMsgHdr->OrFlags(nsMsgMessageFlags::Attachment, &newFlags); + } + } + } + } + else + { + NS_ASSERTION(false, "error creating message header"); + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + else + rv = NS_OK; + + //### why is this stuff const? + char *tmp = (char*) to.value; + PR_Free(tmp); + tmp = (char*) cc.value; + PR_Free(tmp); + + return rv; +} + +nsParseNewMailState::nsParseNewMailState() + : m_disableFilters(false) +{ + m_ibuffer = nullptr; + m_ibuffer_size = 0; + m_ibuffer_fp = 0; + m_numNotNewMessages = 0; + } + +NS_IMPL_ISUPPORTS_INHERITED(nsParseNewMailState, nsMsgMailboxParser, nsIMsgFilterHitNotify) + +nsresult +nsParseNewMailState::Init(nsIMsgFolder *serverFolder, nsIMsgFolder *downloadFolder, + nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aHdr, + nsIOutputStream *aOutputStream) +{ + nsresult rv; + Clear(); + m_rootFolder = serverFolder; + m_msgWindow = aMsgWindow; + m_downloadFolder = downloadFolder; + + m_newMsgHdr = aHdr; + m_outputStream = aOutputStream; + // the new mail parser isn't going to get the stream input, it seems, so we can't use + // the OnStartRequest mechanism the mailbox parser uses. So, let's open the db right now. + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService && !m_mailDB) + rv = msgDBService->OpenFolderDB(downloadFolder, false, + getter_AddRefs(m_mailDB)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgFolder> rootMsgFolder = do_QueryInterface(serverFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = rootMsgFolder->GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv)) + { + rv = server->GetFilterList(aMsgWindow, getter_AddRefs(m_filterList)); + + if (m_filterList) + rv = server->ConfigureTemporaryFilters(m_filterList); + // check if this server defers to another server, in which case + // we'll use that server's filters as well. + nsCOMPtr <nsIMsgFolder> deferredToRootFolder; + server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder)); + if (rootMsgFolder != deferredToRootFolder) + { + nsCOMPtr <nsIMsgIncomingServer> deferredToServer; + deferredToRootFolder->GetServer(getter_AddRefs(deferredToServer)); + if (deferredToServer) + deferredToServer->GetFilterList(aMsgWindow, getter_AddRefs(m_deferredToServerFilterList)); + } + } + m_disableFilters = false; + return NS_OK; +} + +nsParseNewMailState::~nsParseNewMailState() +{ + if (m_mailDB) + m_mailDB->Close(true); + if (m_backupMailDB) + m_backupMailDB->ForceClosed(); +#ifdef DOING_JSFILTERS + JSFilter_cleanup(); +#endif +} + +// not an IMETHOD so we don't need to do error checking or return an error. +// We only have one caller. +void nsParseNewMailState::GetMsgWindow(nsIMsgWindow **aMsgWindow) +{ + NS_IF_ADDREF(*aMsgWindow = m_msgWindow); +} + + +// This gets called for every message because libnet calls IncorporateBegin, +// IncorporateWrite (once or more), and IncorporateComplete for every message. +void nsParseNewMailState::DoneParsingFolder(nsresult status) +{ + /* End of file. Flush out any partial line remaining in the buffer. */ + if (m_ibuffer_fp > 0) + { + ParseFolderLine(m_ibuffer, m_ibuffer_fp); + m_ibuffer_fp = 0; + } + PublishMsgHeader(nullptr); + if (m_mailDB) // finished parsing, so flush db folder info + UpdateDBFolderInfo(); + + /* We're done reading the folder - we don't need these things + any more. */ + PR_FREEIF (m_ibuffer); + m_ibuffer_size = 0; + PR_FREEIF (m_obuffer); + m_obuffer_size = 0; +} + +void nsParseNewMailState::OnNewMessage(nsIMsgWindow *msgWindow) +{ +} + +int32_t nsParseNewMailState::PublishMsgHeader(nsIMsgWindow *msgWindow) +{ + bool moved = false; + FinishHeader(); + + if (m_newMsgHdr) + { + uint32_t newFlags, oldFlags; + m_newMsgHdr->GetFlags(&oldFlags); + if (!(oldFlags & nsMsgMessageFlags::Read)) // don't mark read messages as new. + m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + + if (!m_disableFilters) + { + uint64_t msgOffset; + (void) m_newMsgHdr->GetMessageOffset(&msgOffset); + m_curHdrOffset = msgOffset; + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = m_rootFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, 0); + int32_t duplicateAction; + server->GetIncomingDuplicateAction(&duplicateAction); + if (duplicateAction != nsIMsgIncomingServer::keepDups) + { + bool isDup; + server->IsNewHdrDuplicate(m_newMsgHdr, &isDup); + if (isDup) + { + // we want to do something similar to applying filter hits. + // if a dup is marked read, it shouldn't trigger biff. + // Same for deleting it or moving it to trash. + switch (duplicateAction) + { + case nsIMsgIncomingServer::deleteDups: + { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = + m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) + { + rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr); + if (NS_FAILED(rv)) + m_rootFolder->ThrowAlertMsg("dupDeleteFolderTruncateFailed", msgWindow); + } + m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr); + } + break; + + case nsIMsgIncomingServer::moveDupsToTrash: + { + nsCOMPtr <nsIMsgFolder> trash; + GetTrashFolder(getter_AddRefs(trash)); + if (trash) { + uint32_t newFlags; + bool msgMoved; + m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) + rv = msgStore->MoveNewlyDownloadedMessage(m_newMsgHdr, trash, &msgMoved); + if (NS_SUCCEEDED(rv) && !msgMoved) { + rv = MoveIncorporatedMessage(m_newMsgHdr, m_mailDB, trash, + nullptr, msgWindow); + if (NS_SUCCEEDED(rv)) + rv = m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr); + } + if (NS_FAILED(rv)) + NS_WARNING("moveDupsToTrash failed for some reason."); + } + } + break; + case nsIMsgIncomingServer::markDupsRead: + MarkFilteredMessageRead(m_newMsgHdr); + break; + } + int32_t numNewMessages; + m_downloadFolder->GetNumNewMessages(false, &numNewMessages); + m_downloadFolder->SetNumNewMessages(numNewMessages - 1); + + m_newMsgHdr = nullptr; + return 0; + } + } + + ApplyFilters(&moved, msgWindow, msgOffset); + } + if (!moved) + { + if (m_mailDB) + { + m_mailDB->AddNewHdrToDB(m_newMsgHdr, true); + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgAdded(m_newMsgHdr); + // mark the header as not yet reported classified + nsMsgKey msgKey; + m_newMsgHdr->GetMessageKey(&msgKey); + m_downloadFolder->OrProcessingFlags( + msgKey, nsMsgProcessingFlags::NotReportedClassified); + } + } // if it was moved by imap filter, m_parseMsgState->m_newMsgHdr == nullptr + m_newMsgHdr = nullptr; + } + return 0; +} + +// We've found the start of the next message, so finish this one off. +NS_IMETHODIMP nsParseNewMailState::FinishHeader() +{ + if (m_newMsgHdr) + { + m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos); + m_newMsgHdr->SetLineCount(m_body_lines); + } + return NS_OK; +} + +nsresult nsParseNewMailState::GetTrashFolder(nsIMsgFolder **pTrashFolder) +{ + nsresult rv=NS_ERROR_UNEXPECTED; + if (!pTrashFolder) + return NS_ERROR_NULL_POINTER; + + if (m_downloadFolder) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + m_downloadFolder->GetServer(getter_AddRefs(incomingServer)); + nsCOMPtr <nsIMsgFolder> rootMsgFolder; + incomingServer->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + if (rootMsgFolder) + { + rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder); + if (!*pTrashFolder) + rv = NS_ERROR_FAILURE; + } + } + return rv; +} + +void nsParseNewMailState::ApplyFilters(bool *pMoved, nsIMsgWindow *msgWindow, uint64_t msgOffset) +{ + m_msgMovedByFilter = m_msgCopiedByFilter = false; + m_curHdrOffset = msgOffset; + + if (!m_disableFilters) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr; + nsCOMPtr<nsIMsgFolder> downloadFolder = m_downloadFolder; + nsCOMPtr <nsIMsgFolder> rootMsgFolder = do_QueryInterface(m_rootFolder); + if (rootMsgFolder) + { + if (!downloadFolder) + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(downloadFolder)); + if (downloadFolder) + downloadFolder->GetURI(m_inboxUri); + char * headers = m_headers.GetBuffer(); + uint32_t headersSize = m_headers.GetBufferPos(); + if (m_filterList) + (void) m_filterList-> + ApplyFiltersToHdr(nsMsgFilterType::InboxRule, msgHdr, downloadFolder, + m_mailDB, headers, headersSize, this, msgWindow); + if (!m_msgMovedByFilter && m_deferredToServerFilterList) + { + (void) m_deferredToServerFilterList-> + ApplyFiltersToHdr(nsMsgFilterType::InboxRule, msgHdr, downloadFolder, + m_mailDB, headers, headersSize, this, msgWindow); + } + } + } + if (pMoved) + *pMoved = m_msgMovedByFilter; +} + +NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindow *msgWindow, bool *applyMore) +{ + NS_ENSURE_ARG_POINTER(filter); + NS_ENSURE_ARG_POINTER(applyMore); + + uint32_t newFlags; + nsresult rv = NS_OK; + + *applyMore = true; + + nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr; + + nsCOMPtr<nsIArray> filterActionList; + + rv = filter->GetSortedActionList(getter_AddRefs(filterActionList)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numActions; + rv = filterActionList->GetLength(&numActions); + NS_ENSURE_SUCCESS(rv, rv); + + bool loggingEnabled = false; + if (m_filterList && numActions) + m_filterList->GetLoggingEnabled(&loggingEnabled); + + bool msgIsNew = true; + for (uint32_t actionIndex = 0; actionIndex < numActions && *applyMore; actionIndex++) + { + nsCOMPtr<nsIMsgRuleAction> filterAction; + rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction), + getter_AddRefs(filterAction)); + if (NS_FAILED(rv) || !filterAction) + continue; + + nsMsgRuleActionType actionType; + if (NS_SUCCEEDED(filterAction->GetType(&actionType))) + { + if (loggingEnabled) + (void)filter->LogRuleHit(filterAction, msgHdr); + + nsCString actionTargetFolderUri; + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) + { + + rv = filterAction->GetTargetFolderUri(actionTargetFolderUri); + if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) + { + NS_ASSERTION(false, "actionTargetFolderUri is empty"); + continue; + } + } + switch (actionType) + { + case nsMsgFilterAction::Delete: + { + nsCOMPtr <nsIMsgFolder> trash; + // set value to trash folder + rv = GetTrashFolder(getter_AddRefs(trash)); + if (NS_SUCCEEDED(rv) && trash) + rv = trash->GetURI(actionTargetFolderUri); + + msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark read in trash. + msgIsNew = false; + } + + // FALLTHROUGH + MOZ_FALLTHROUGH; + case nsMsgFilterAction::MoveToFolder: + // if moving to a different file, do it. + if (actionTargetFolderUri.get() && !m_inboxUri.Equals(actionTargetFolderUri, + nsCaseInsensitiveCStringComparator())) + { + nsresult err; + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &err)); + NS_ENSURE_SUCCESS(err, err); + nsCOMPtr<nsIRDFResource> res; + err = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res)); + if (NS_FAILED(err)) + return err; + + nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &err)); + if (NS_FAILED(err)) + return err; + bool msgMoved = false; + // If we're moving to an imap folder, or this message has already + // has a pending copy action, use the imap coalescer so that + // we won't truncate the inbox before the copy fires. + if (m_msgCopiedByFilter || + StringBeginsWith(actionTargetFolderUri, NS_LITERAL_CSTRING("imap:"))) + { + if (!m_moveCoalescer) + m_moveCoalescer = new nsImapMoveCoalescer(m_downloadFolder, m_msgWindow); + NS_ENSURE_TRUE(m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY); + nsMsgKey msgKey; + (void) msgHdr->GetMessageKey(&msgKey); + m_moveCoalescer->AddMove(destIFolder, msgKey); + err = NS_OK; + msgIsNew = false; + } + else + { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + err = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(err)) + err = msgStore->MoveNewlyDownloadedMessage(msgHdr, destIFolder, &msgMoved); + if (NS_SUCCEEDED(err) && !msgMoved) + err = MoveIncorporatedMessage(msgHdr, m_mailDB, destIFolder, + filter, msgWindow); + m_msgMovedByFilter = NS_SUCCEEDED(err); + if (!m_msgMovedByFilter /* == NS_FAILED(err) */) + { + // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands. + if (loggingEnabled) + (void) filter->LogRuleHitFail(filterAction, msgHdr, err, "Move failed"); + } + } + } + *applyMore = false; + break; + + case nsMsgFilterAction::CopyToFolder: + { + nsCString uri; + rv = m_rootFolder->GetURI(uri); + + if (!actionTargetFolderUri.IsEmpty() && !actionTargetFolderUri.Equals(uri)) + { + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + + nsCOMPtr<nsIMsgFolder> dstFolder; + nsCOMPtr<nsIMsgCopyService> copyService; + rv = GetExistingFolder(actionTargetFolderUri, + getter_AddRefs(dstFolder)); + if (NS_SUCCEEDED(rv)) { + copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + } + else { + // Let's show a more specific warning. + NS_WARNING("Target Folder does not exist."); + return rv; + } + if (NS_SUCCEEDED(rv)) + rv = copyService->CopyMessages(m_downloadFolder, messageArray, dstFolder, + false, nullptr, msgWindow, false); + + if (NS_FAILED(rv)) { + // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands. + if (loggingEnabled) + (void) filter->LogRuleHitFail(filterAction, msgHdr, rv, "Copy failed"); + } + else + m_msgCopiedByFilter = true; + } + } + break; + case nsMsgFilterAction::MarkRead: + msgIsNew = false; + MarkFilteredMessageRead(msgHdr); + break; + case nsMsgFilterAction::MarkUnread: + msgIsNew = true; + MarkFilteredMessageUnread(msgHdr); + break; + case nsMsgFilterAction::KillThread: + msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored); + break; + case nsMsgFilterAction::KillSubthread: + msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags); + break; + case nsMsgFilterAction::WatchThread: + msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags); + break; + case nsMsgFilterAction::MarkFlagged: + { + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->MarkMessagesFlagged(messageArray, true); + } + break; + case nsMsgFilterAction::ChangePriority: + nsMsgPriorityValue filterPriority; + filterAction->GetPriority(&filterPriority); + msgHdr->SetPriority(filterPriority); + break; + case nsMsgFilterAction::AddTag: + { + nsCString keyword; + filterAction->GetStrValue(keyword); + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->AddKeywordsToMessages(messageArray, keyword); + break; + } + case nsMsgFilterAction::Label: + nsMsgLabelValue filterLabel; + filterAction->GetLabel(&filterLabel); + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + m_mailDB->SetLabel(msgKey, filterLabel); + break; + case nsMsgFilterAction::JunkScore: + { + nsAutoCString junkScoreStr; + int32_t junkScore; + filterAction->GetJunkScore(&junkScore); + junkScoreStr.AppendInt(junkScore); + if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) + msgIsNew = false; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + msgHdr->SetStringProperty("junkscore", junkScoreStr.get()); + msgHdr->SetStringProperty("junkscoreorigin", "filter"); + break; + } + case nsMsgFilterAction::Forward: + { + nsCString forwardTo; + filterAction->GetStrValue(forwardTo); + m_forwardTo.AppendElement(forwardTo); + m_msgToForwardOrReply = msgHdr; + } + break; + case nsMsgFilterAction::Reply: + { + nsCString replyTemplateUri; + filterAction->GetStrValue(replyTemplateUri); + m_replyTemplateUri.AppendElement(replyTemplateUri); + m_msgToForwardOrReply = msgHdr; + m_ruleAction = filterAction; + m_filter = filter; + } + break; + case nsMsgFilterAction::DeleteFromPop3Server: + { + uint32_t flags = 0; + nsCOMPtr <nsIMsgFolder> downloadFolder; + msgHdr->GetFolder(getter_AddRefs(downloadFolder)); + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(downloadFolder); + msgHdr->GetFlags(&flags); + if (localFolder) + { + nsCOMPtr<nsIMutableArray> messages = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + messages->AppendElement(msgHdr, false); + // This action ignores the deleteMailLeftOnServer preference + localFolder->MarkMsgsOnPop3Server(messages, POP3_FORCE_DEL); + + // If this is just a header, throw it away. It's useless now + // that the server copy is being deleted. + if (flags & nsMsgMessageFlags::Partial) + { + m_msgMovedByFilter = true; + msgIsNew = false; + } + } + } + break; + case nsMsgFilterAction::FetchBodyFromPop3Server: + { + uint32_t flags = 0; + nsCOMPtr <nsIMsgFolder> downloadFolder; + msgHdr->GetFolder(getter_AddRefs(downloadFolder)); + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(downloadFolder); + msgHdr->GetFlags(&flags); + if (localFolder && (flags & nsMsgMessageFlags::Partial)) + { + nsCOMPtr<nsIMutableArray> messages = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + messages->AppendElement(msgHdr, false); + localFolder->MarkMsgsOnPop3Server(messages, POP3_FETCH_BODY); + // Don't add this header to the DB, we're going to replace it + // with the full message. + m_msgMovedByFilter = true; + msgIsNew = false; + // Don't do anything else in this filter, wait until we + // have the full message. + *applyMore = false; + } + } + break; + + case nsMsgFilterAction::StopExecution: + { + // don't apply any more filters + *applyMore = false; + } + break; + + case nsMsgFilterAction::Custom: + { + nsCOMPtr<nsIMsgFilterCustomAction> customAction; + rv = filterAction->GetCustomAction(getter_AddRefs(customAction)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString value; + filterAction->GetStrValue(value); + + nsCOMPtr<nsIMutableArray> messageArray( + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_TRUE(messageArray, rv); + if (NS_SUCCEEDED(rv)) + rv = messageArray->AppendElement(msgHdr, false); + + + if (NS_SUCCEEDED(rv)) + rv = customAction->Apply(messageArray, value, nullptr, + nsMsgFilterType::InboxRule, msgWindow); + if (NS_FAILED(rv)) { + // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands. + if (loggingEnabled) + (void) filter->LogRuleHitFail(filterAction, msgHdr, rv, "Copy failed"); + } + } + break; + + + default: + // XXX should not be reached. Check in debug build. + NS_ERROR("This default should not be reached."); + break; + } + } + } + if (!msgIsNew) + { + int32_t numNewMessages; + m_downloadFolder->GetNumNewMessages(false, &numNewMessages); + if (numNewMessages > 0) + m_downloadFolder->SetNumNewMessages(numNewMessages - 1); + m_numNotNewMessages++; + } + return rv; +} + +// this gets run in a second pass, after apply filters to a header. +nsresult nsParseNewMailState::ApplyForwardAndReplyFilter(nsIMsgWindow *msgWindow) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgIncomingServer> server; + + uint32_t i; + uint32_t count = m_forwardTo.Length(); + for (i = 0; i < count; i++) + { + if (!m_forwardTo[i].IsEmpty()) + { + nsAutoString forwardStr; + CopyASCIItoUTF16(m_forwardTo[i], forwardStr); + rv = m_rootFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + { + nsCOMPtr<nsIMsgComposeService> compService = + do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = compService->ForwardMessage(forwardStr, m_msgToForwardOrReply, + msgWindow, server, + nsIMsgComposeService::kForwardAsDefault); + } + } + } + m_forwardTo.Clear(); + + count = m_replyTemplateUri.Length(); + for (i = 0; i < count; i++) + { + if (!m_replyTemplateUri[i].IsEmpty()) + { + // copy this and truncate the original, so we don't accidentally re-use it on the next hdr. + rv = m_rootFolder->GetServer(getter_AddRefs(server)); + if (server) + { + nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ; + if (compService) { + rv = compService->ReplyWithTemplate(m_msgToForwardOrReply, + m_replyTemplateUri[i].get(), + msgWindow, server); + if (NS_FAILED(rv)) { + NS_WARNING("ReplyWithTemplate failed"); + if (rv == NS_ERROR_ABORT) { + m_filter->LogRuleHitFail(m_ruleAction, m_msgToForwardOrReply, rv, + "Sending reply aborted"); + } else { + m_filter->LogRuleHitFail(m_ruleAction, m_msgToForwardOrReply, rv, + "Error sending reply"); + } + } + } + } + } + } + m_replyTemplateUri.Clear(); + m_msgToForwardOrReply = nullptr; + return rv; +} + +void nsParseNewMailState::MarkFilteredMessageRead(nsIMsgDBHdr *msgHdr) +{ + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->MarkMessagesRead(messageArray, true); +} + +void nsParseNewMailState::MarkFilteredMessageUnread(nsIMsgDBHdr *msgHdr) +{ + uint32_t newFlags; + if (m_mailDB) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + m_mailDB->AddToNewList(msgKey); + } + else + { + msgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + } + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->MarkMessagesRead(messageArray, false); +} + +nsresult nsParseNewMailState::EndMsgDownload() +{ + if (m_moveCoalescer) + m_moveCoalescer->PlaybackMoves(); + + // need to do this for all folders that had messages filtered into them + uint32_t serverCount = m_filterTargetFolders.Count(); + nsresult rv; + nsCOMPtr<nsIMsgMailSession> session = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && session) // don't use NS_ENSURE_SUCCESS here - we need to release semaphore below + { + for (uint32_t index = 0; index < serverCount; index++) + { + bool folderOpen; + session->IsFolderOpenInWindow(m_filterTargetFolders[index], &folderOpen); + if (!folderOpen) + { + uint32_t folderFlags; + m_filterTargetFolders[index]->GetFlags(&folderFlags); + if (! (folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) + { + bool filtersRun; + m_filterTargetFolders[index]->CallFilterPlugins(nullptr, &filtersRun); + if (!filtersRun) + m_filterTargetFolders[index]->SetMsgDatabase(nullptr); + } + } + } + } + m_filterTargetFolders.Clear(); + return rv; +} + +nsresult nsParseNewMailState::AppendMsgFromStream(nsIInputStream *fileStream, + nsIMsgDBHdr *aHdr, + uint32_t length, + nsIMsgFolder *destFolder) +{ + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(fileStream); + nsCOMPtr<nsIMsgPluggableStore> store; + nsCOMPtr<nsIOutputStream> destOutputStream; + nsresult rv = destFolder->GetMsgStore(getter_AddRefs(store)); + NS_ENSURE_SUCCESS(rv, rv); + bool reusable; + rv = store->GetNewMsgOutputStream(destFolder, &aHdr, &reusable, + getter_AddRefs(destOutputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_ibuffer) + { + m_ibuffer_size = FILE_IO_BUFFER_SIZE; + m_ibuffer = (char *) PR_Malloc(m_ibuffer_size); + NS_ASSERTION(m_ibuffer != nullptr, "couldn't get memory to move msg"); + } + m_ibuffer_fp = 0; + + while (length > 0 && m_ibuffer) + { + uint32_t nRead; + fileStream->Read (m_ibuffer, length > m_ibuffer_size ? m_ibuffer_size : length, &nRead); + if (nRead == 0) + break; + + uint32_t bytesWritten; + // Check the number of bytes actually written to the stream. + destOutputStream->Write(m_ibuffer, nRead, &bytesWritten); + if (bytesWritten != nRead) + { + destOutputStream->Close(); + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + length -= nRead; + } + + NS_ASSERTION(length == 0, "didn't read all of original message in filter move"); + + // non-reusable streams will get closed by the store. + if (reusable) + destOutputStream->Close(); + return store->FinishNewMessage(destOutputStream, aHdr); +} + +/* + * Moves message pointed to by mailHdr into folder destIFolder. + * After successful move mailHdr is no longer usable by the caller. + */ +nsresult nsParseNewMailState::MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr, + nsIMsgDatabase *sourceDB, + nsIMsgFolder *destIFolder, + nsIMsgFilter *filter, + nsIMsgWindow *msgWindow) +{ + NS_ENSURE_ARG_POINTER(destIFolder); + nsresult rv = NS_OK; + + // check if the destination is a real folder (by checking for null parent) + // and if it can file messages (e.g., servers or news folders can't file messages). + // Or read only imap folders... + bool canFileMessages = true; + nsCOMPtr<nsIMsgFolder> parentFolder; + destIFolder->GetParent(getter_AddRefs(parentFolder)); + if (parentFolder) + destIFolder->GetCanFileMessages(&canFileMessages); + if (!parentFolder || !canFileMessages) + { + if (filter) + { + filter->SetEnabled(false); + // we need to explicitly save the filter file. + if (m_filterList) + m_filterList->SaveToDefaultFile(); + destIFolder->ThrowAlertMsg("filterDisabled", msgWindow); + } + return NS_MSG_NOT_A_MAIL_FOLDER; + } + + uint32_t messageLength; + mailHdr->GetMessageSize(&messageLength); + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(destIFolder); + if (localFolder) { + bool destFolderTooBig = true; + rv = localFolder->WarnIfLocalFileTooBig(msgWindow, messageLength, + &destFolderTooBig); + if (NS_FAILED(rv) || destFolderTooBig) + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + nsCOMPtr<nsISupports> myISupports = + do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this)); + + // Make sure no one else is writing into this folder + if (NS_FAILED(rv = destIFolder->AcquireSemaphore (myISupports))) + { + destIFolder->ThrowAlertMsg("filterFolderDeniedLocked", msgWindow); + return rv; + } + nsCOMPtr<nsIInputStream> inputStream; + bool reusable; + rv = m_downloadFolder->GetMsgInputStream(mailHdr, &reusable, getter_AddRefs(inputStream)); + if (!inputStream) + { + NS_ERROR("couldn't get source msg input stream in move filter"); + destIFolder->ReleaseSemaphore (myISupports); + return NS_MSG_FOLDER_UNREADABLE; // ### dmb + } + + nsCOMPtr<nsIMsgDatabase> destMailDB; + + if (!localFolder) + return NS_MSG_POP_FILTER_TARGET_ERROR; + + // don't force upgrade in place - open the db here before we start writing to the + // destination file because XP_Stat can return file size including bytes written... + rv = localFolder->GetDatabaseWOReparse(getter_AddRefs(destMailDB)); + NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv), + "failed to open mail db parsing folder"); + nsCOMPtr<nsIMsgDBHdr> newHdr; + + if (destMailDB) + rv = destMailDB->CopyHdrFromExistingHdr(m_new_key, mailHdr, true, + getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && !newHdr) + rv = NS_ERROR_UNEXPECTED; + + if (NS_FAILED(rv)) + { + destIFolder->ThrowAlertMsg("filterFolderHdrAddFailed", msgWindow); + } else { + rv = AppendMsgFromStream(inputStream, newHdr, messageLength, destIFolder); + if (NS_FAILED(rv)) + destIFolder->ThrowAlertMsg("filterFolderWriteFailed", msgWindow); + } + + if (NS_FAILED(rv)) + { + if (destMailDB) + destMailDB->Close(true); + + destIFolder->ReleaseSemaphore(myISupports); + + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + bool movedMsgIsNew = false; + // if we have made it this far then the message has successfully been written to the new folder + // now add the header to the destMailDB. + + uint32_t newFlags; + newHdr->GetFlags(&newFlags); + nsMsgKey msgKey; + newHdr->GetMessageKey(&msgKey); + if (!(newFlags & nsMsgMessageFlags::Read)) + { + nsCString junkScoreStr; + (void) newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_HAM_SCORE) + { + newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + destMailDB->AddToNewList(msgKey); + movedMsgIsNew = true; + } + } + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgAdded(newHdr); + // mark the header as not yet reported classified + destIFolder->OrProcessingFlags( + msgKey, nsMsgProcessingFlags::NotReportedClassified); + m_msgToForwardOrReply = newHdr; + + if (movedMsgIsNew) + destIFolder->SetHasNewMessages(true); + if (!m_filterTargetFolders.Contains(destIFolder)) + m_filterTargetFolders.AppendObject(destIFolder); + + destIFolder->ReleaseSemaphore (myISupports); + + (void) localFolder->RefreshSizeOnDisk(); + + // Notify the message was moved. + if (notifier) { + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = mailHdr->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv)) { + notifier->NotifyItemEvent(folder, + NS_LITERAL_CSTRING("UnincorporatedMessageMoved"), + newHdr); + } else { + NS_WARNING("Can't get folder for message that was moved."); + } + } + + nsCOMPtr<nsIMsgPluggableStore> store; + rv = m_downloadFolder->GetMsgStore(getter_AddRefs(store)); + if (store) + store->DiscardNewMessage(m_outputStream, mailHdr); + if (sourceDB) + sourceDB->RemoveHeaderMdbRow(mailHdr); + + // update the folder size so we won't reparse. + UpdateDBFolderInfo(destMailDB); + destIFolder->UpdateSummaryTotals(true); + + destMailDB->Commit(nsMsgDBCommitType::kLargeCommit); + return rv; +} diff --git a/mailnews/local/src/nsParseMailbox.h b/mailnews/local/src/nsParseMailbox.h new file mode 100644 index 000000000..388f1e1b2 --- /dev/null +++ b/mailnews/local/src/nsParseMailbox.h @@ -0,0 +1,271 @@ +/* -*- 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/. */ + +#ifndef nsParseMailbox_H +#define nsParseMailbox_H + +#include "mozilla/Attributes.h" +#include "nsIURI.h" +#include "nsIMsgParseMailMsgState.h" +#include "nsIStreamListener.h" +#include "nsMsgLineBuffer.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsIMsgStatusFeedback.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIDBChangeListener.h" +#include "nsIWeakReference.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIMsgWindow.h" +#include "nsImapMoveCoalescer.h" +#include "nsAutoPtr.h" +#include "nsStringGlue.h" +#include "nsIMsgFilterList.h" +#include "nsIMsgFilter.h" +#include "nsIMsgFilterHitNotify.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsTArray.h" + +class nsByteArray; +class nsOutputFileStream; +class nsIOFileStream; +class nsInputFileStream; +class nsIMsgFilter; +class MSG_FolderInfoMail; +class nsIMsgFilterList; +class nsIMsgFolder; + +/* Used for the various things that parse RFC822 headers... + */ +typedef struct message_header +{ + const char *value; /* The contents of a header (after ": ") */ + int32_t length; /* The length of the data (it is not NULL-terminated.) */ +} message_header; + +// This object maintains the parse state for a single mail message. +class nsParseMailMessageState : public nsIMsgParseMailMsgState, public nsIDBChangeListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPARSEMAILMSGSTATE + NS_DECL_NSIDBCHANGELISTENER + + nsParseMailMessageState(); + + void Init(uint64_t fileposition); + virtual nsresult ParseFolderLine(const char *line, uint32_t lineLength); + virtual nsresult StartNewEnvelope(const char *line, uint32_t lineLength); + nsresult ParseHeaders(); + nsresult FinalizeHeaders(); + nsresult ParseEnvelope (const char *line, uint32_t line_size); + nsresult InternSubject (struct message_header *header); + + static bool IsEnvelopeLine(const char *buf, int32_t buf_size); + + nsCOMPtr<nsIMsgDBHdr> m_newMsgHdr; /* current message header we're building */ + nsCOMPtr<nsIMsgDatabase> m_mailDB; + nsCOMPtr<nsIMsgDatabase> m_backupMailDB; + + nsMailboxParseState m_state; + int64_t m_position; + uint64_t m_envelope_pos; + uint64_t m_headerstartpos; + nsMsgKey m_new_key; // DB key for the new header. + + nsByteArray m_headers; + + nsByteArray m_envelope; + + struct message_header m_message_id; + struct message_header m_references; + struct message_header m_date; + struct message_header m_delivery_date; + struct message_header m_from; + struct message_header m_sender; + struct message_header m_newsgroups; + struct message_header m_subject; + struct message_header m_status; + struct message_header m_mozstatus; + struct message_header m_mozstatus2; + struct message_header m_in_reply_to; + struct message_header m_replyTo; + struct message_header m_content_type; + struct message_header m_bccList; + + // Support for having multiple To or Cc header lines in a message + nsTArray<struct message_header*> m_toList; + nsTArray<struct message_header*> m_ccList; + struct message_header *GetNextHeaderInAggregate (nsTArray<struct message_header*> &list); + void GetAggregateHeader (nsTArray<struct message_header*> &list, struct message_header *); + void ClearAggregateHeader (nsTArray<struct message_header*> &list); + + struct message_header m_envelope_from; + struct message_header m_envelope_date; + struct message_header m_priority; + struct message_header m_account_key; + struct message_header m_keywords; + // Mdn support + struct message_header m_mdn_original_recipient; + struct message_header m_return_path; + struct message_header m_mdn_dnt; /* MDN Disposition-Notification-To: header */ + + PRTime m_receivedTime; + uint16_t m_body_lines; + + bool m_IgnoreXMozillaStatus; + + // this enables extensions to add the values of particular headers to + // the .msf file as properties of nsIMsgHdr. It is initialized from a + // pref, mailnews.customDBHeaders + nsTArray<nsCString> m_customDBHeaders; + struct message_header *m_customDBHeaderValues; + nsCString m_receivedValue; // accumulated received header +protected: + virtual ~nsParseMailMessageState(); +}; + +// This class is part of the mailbox parsing state machine +class nsMsgMailboxParser : public nsIStreamListener, public nsParseMailMessageState, public nsMsgLineBuffer +{ +public: + nsMsgMailboxParser(nsIMsgFolder *); + nsMsgMailboxParser(); + nsresult Init(); + + bool IsRunningUrl() { return m_urlInProgress;} // returns true if we are currently running a url and false otherwise... + NS_DECL_ISUPPORTS_INHERITED + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIStreamListener interface + //////////////////////////////////////////////////////////////////////////////////////// + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + void SetDB (nsIMsgDatabase *mailDB) {m_mailDB = mailDB; } + + // message socket libnet callbacks, which come through folder pane + nsresult ProcessMailboxInputStream(nsIURI* aURL, nsIInputStream *aIStream, uint32_t aLength); + + virtual void DoneParsingFolder(nsresult status); + virtual void AbortNewHeader(); + + // for nsMsgLineBuffer + virtual nsresult HandleLine(const char *line, uint32_t line_length) override; + + void UpdateDBFolderInfo(); + void UpdateDBFolderInfo(nsIMsgDatabase *mailDB); + void UpdateStatusText(const char* stringName); + + // Update the progress bar based on what we know. + virtual void UpdateProgressPercent (); + virtual void OnNewMessage(nsIMsgWindow *msgWindow); + +protected: + virtual ~nsMsgMailboxParser(); + nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback; + + virtual int32_t PublishMsgHeader(nsIMsgWindow *msgWindow); + void FreeBuffers(); + + // data + nsString m_folderName; + nsCString m_inboxUri; + nsByteArray m_inputStream; + int32_t m_obuffer_size; + char *m_obuffer; + uint64_t m_graph_progress_total; + uint64_t m_graph_progress_received; + bool m_parsingDone; + PRTime m_startTime; +private: + // the following flag is used to determine when a url is currently being run. It is cleared on calls + // to ::StopBinding and it is set whenever we call Load on a url + bool m_urlInProgress; + nsWeakPtr m_folder; + void ReleaseFolderLock(); + nsresult AcquireFolderLock(); + +}; + +class nsParseNewMailState : public nsMsgMailboxParser +, public nsIMsgFilterHitNotify +{ +public: + nsParseNewMailState(); + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD FinishHeader() override; + + nsresult Init(nsIMsgFolder *rootFolder, nsIMsgFolder *downloadFolder, + nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aHdr, + nsIOutputStream *aOutputStream); + + virtual void DoneParsingFolder(nsresult status) override; + + void DisableFilters() {m_disableFilters = true;} + + NS_DECL_NSIMSGFILTERHITNOTIFY + + nsOutputFileStream *GetLogFile(); + virtual int32_t PublishMsgHeader(nsIMsgWindow *msgWindow) override; + void GetMsgWindow(nsIMsgWindow **aMsgWindow); + nsresult EndMsgDownload(); + + nsresult AppendMsgFromStream(nsIInputStream *fileStream, nsIMsgDBHdr *aHdr, + uint32_t length, nsIMsgFolder *destFolder); + + virtual void ApplyFilters(bool *pMoved, nsIMsgWindow *msgWindow, + uint64_t msgOffset); + nsresult ApplyForwardAndReplyFilter(nsIMsgWindow *msgWindow); + virtual void OnNewMessage(nsIMsgWindow *msgWindow) override; + + // this keeps track of how many messages we downloaded that + // aren't new - e.g., marked read, or moved to an other server. + int32_t m_numNotNewMessages; +protected: + virtual ~nsParseNewMailState(); + virtual nsresult GetTrashFolder(nsIMsgFolder **pTrashFolder); + virtual nsresult MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr, + nsIMsgDatabase *sourceDB, + nsIMsgFolder *destIFolder, + nsIMsgFilter *filter, + nsIMsgWindow *msgWindow); + virtual void MarkFilteredMessageRead(nsIMsgDBHdr *msgHdr); + virtual void MarkFilteredMessageUnread(nsIMsgDBHdr *msgHdr); + + nsCOMPtr <nsIMsgFilterList> m_filterList; + nsCOMPtr <nsIMsgFilterList> m_deferredToServerFilterList; + nsCOMPtr <nsIMsgFolder> m_rootFolder; + nsCOMPtr <nsIMsgWindow> m_msgWindow; + nsCOMPtr <nsIMsgFolder> m_downloadFolder; + nsCOMPtr<nsIOutputStream> m_outputStream; + nsCOMArray <nsIMsgFolder> m_filterTargetFolders; + + RefPtr<nsImapMoveCoalescer> m_moveCoalescer; + + bool m_msgMovedByFilter; + bool m_msgCopiedByFilter; + bool m_disableFilters; + uint32_t m_ibuffer_fp; + char *m_ibuffer; + uint32_t m_ibuffer_size; + // used for applying move filters, because in the case of using a temporary + // download file, the offset/key in the msg hdr is not right. + uint64_t m_curHdrOffset; + + // we have to apply the reply/forward filters in a second pass, after + // msg quarantining and moving to other local folders, so we remember the + // info we'll need to apply them with these vars. + // these need to be arrays in case we have multiple reply/forward filters. + nsTArray<nsCString> m_forwardTo; + nsTArray<nsCString> m_replyTemplateUri; + nsCOMPtr<nsIMsgDBHdr> m_msgToForwardOrReply; + nsCOMPtr<nsIMsgFilter> m_filter; + nsCOMPtr<nsIMsgRuleAction> m_ruleAction; +}; + +#endif diff --git a/mailnews/local/src/nsPop3IncomingServer.cpp b/mailnews/local/src/nsPop3IncomingServer.cpp new file mode 100644 index 000000000..261d141a4 --- /dev/null +++ b/mailnews/local/src/nsPop3IncomingServer.cpp @@ -0,0 +1,744 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "prmem.h" +#include "plstr.h" +#include "prprf.h" + +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsIPop3IncomingServer.h" +#include "nsPop3IncomingServer.h" +#include "nsIPop3Service.h" +#include "nsMsgBaseCID.h" +#include "nsMsgLocalCID.h" +#include "nsMsgFolderFlags.h" +#include "nsPop3Protocol.h" +#include "nsAutoPtr.h" +#include "nsMsgKeyArray.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsServiceManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsMsgUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Likely.h" + +static NS_DEFINE_CID(kCPop3ServiceCID, NS_POP3SERVICE_CID); + +class nsPop3GetMailChainer final : public nsIUrlListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + + nsPop3GetMailChainer(); + nsresult GetNewMailForServers(nsIPop3IncomingServer** servers, uint32_t count, + nsIMsgWindow *msgWindow, + nsIMsgFolder *folderToDownloadTo, nsIUrlListener *listener); + nsresult RunNextGetNewMail(); +protected: + ~nsPop3GetMailChainer(); + nsCOMPtr <nsIMsgFolder> m_folderToDownloadTo; + nsCOMPtr <nsIMsgWindow> m_downloadingMsgWindow; + nsCOMArray<nsIPop3IncomingServer> m_serversToGetNewMailFor; + nsCOMPtr <nsIUrlListener> m_listener; +}; + + + +NS_IMPL_ISUPPORTS_INHERITED(nsPop3IncomingServer, + nsMsgIncomingServer, + nsIPop3IncomingServer, + nsILocalMailIncomingServer) + +nsPop3IncomingServer::nsPop3IncomingServer() +{ + m_capabilityFlags = + POP3_AUTH_MECH_UNDEFINED | + POP3_HAS_AUTH_USER | // should be always there + POP3_GURL_UNDEFINED | + POP3_UIDL_UNDEFINED | + POP3_TOP_UNDEFINED | + POP3_XTND_XLST_UNDEFINED; + + m_canHaveFilters = true; + m_authenticated = false; +} + +nsPop3IncomingServer::~nsPop3IncomingServer() +{ +} + +NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer, + LeaveMessagesOnServer, + "leave_on_server") + +NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer, + HeadersOnly, + "headers_only") + +NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer, + DeleteMailLeftOnServer, + "delete_mail_left_on_server") + +NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer, + DotFix, + "dot_fix") + +NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer, + DeleteByAgeFromServer, + "delete_by_age_from_server") + +NS_IMPL_SERVERPREF_INT(nsPop3IncomingServer, + NumDaysToLeaveOnServer, + "num_days_to_leave_on_server") + + +NS_IMPL_SERVERPREF_BOOL(nsPop3IncomingServer, + DeferGetNewMail, + "defer_get_new_mail") + +NS_IMETHODIMP nsPop3IncomingServer::GetDeferredToAccount(nsACString& aRetVal) +{ + nsresult rv = GetCharValue("deferred_to_account", aRetVal); + if (aRetVal.IsEmpty()) + return rv; + // We need to repair broken profiles that defer to hidden or invalid servers, + // so find out if the deferred to account has a valid non-hidden server, and + // if not, defer to the local folders inbox. + nsCOMPtr<nsIMsgAccountManager> acctMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID); + bool invalidAccount = true; + if (acctMgr) + { + nsCOMPtr<nsIMsgAccount> account; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = acctMgr->GetAccount(aRetVal, getter_AddRefs(account)); + if (account) + { + account->GetIncomingServer(getter_AddRefs(server)); + if (server) + server->GetHidden(&invalidAccount); + } + if (invalidAccount) + { + nsCOMPtr<nsIMsgIncomingServer> localServer; + nsCOMPtr<nsIMsgAccount> localAccount; + + rv = acctMgr->GetLocalFoldersServer(getter_AddRefs(localServer)); + NS_ENSURE_SUCCESS(rv, rv); + // Try to copy any folders that have been stranded in the hidden account + // into the local folders account. + if (server) + { + nsCOMPtr<nsIMsgFolder> hiddenRootFolder; + nsCOMPtr<nsIMsgFolder> localFoldersRoot; + server->GetRootFolder(getter_AddRefs(hiddenRootFolder)); + localServer->GetRootFolder(getter_AddRefs(localFoldersRoot)); + if (hiddenRootFolder && localFoldersRoot) + { + // We're going to iterate over the folders in Local Folders-1, + // though I suspect only the Inbox will have messages. I don't + // think Sent Mail could end up here, but if any folders have + // messages, might as well copy them to the real Local Folders + // account. + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = hiddenRootFolder->GetSubFolders(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(rv)) + { + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + nsCOMPtr<nsIMsgFolder> subFolder(do_QueryInterface(item)); + if (subFolder) + { + nsCOMPtr<nsIMsgDatabase> subFolderDB; + subFolder->GetMsgDatabase(getter_AddRefs(subFolderDB)); + if (subFolderDB) + { + // Copy any messages in this sub-folder of the hidden + // account to the corresponding folder in Local Folders. + RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray; + rv = subFolderDB->ListAllKeys(keys); + nsCOMPtr<nsIMutableArray> hdrsToCopy(do_CreateInstance(NS_ARRAY_CONTRACTID)); + MsgGetHeadersFromKeys(subFolderDB, keys->m_keys, hdrsToCopy); + uint32_t numHdrs = 0; + if (hdrsToCopy) + hdrsToCopy->GetLength(&numHdrs); + if (numHdrs) + { + // Look for a folder with the same name in Local Folders. + nsCOMPtr<nsIMsgFolder> dest; + nsString folderName; + subFolder->GetName(folderName); + localFoldersRoot->GetChildNamed(folderName, + getter_AddRefs(dest)); + if (dest) + dest->CopyMessages(subFolder, hdrsToCopy, false, + nullptr, nullptr, false,false); + // Should we copy the folder if the dest doesn't exist? + } + } + } + } + } + } + } + rv = acctMgr->FindAccountForServer(localServer, getter_AddRefs(localAccount)); + NS_ENSURE_SUCCESS(rv, rv); + if (!localAccount) + return NS_ERROR_NOT_AVAILABLE; + + localAccount->GetKey(aRetVal); + // Can't call SetDeferredToAccount because it calls GetDeferredToAccount. + return SetCharValue("deferred_to_account", aRetVal); + } + } + return rv; +} + +NS_IMETHODIMP nsPop3IncomingServer::SetDeferredToAccount(const nsACString& aAccountKey) +{ + nsCString deferredToAccount; + GetDeferredToAccount(deferredToAccount); + m_rootMsgFolder = nullptr; // clear this so we'll recalculate it on demand. + //Notify listeners who listen to every folder + + nsresult rv = SetCharValue("deferred_to_account", aAccountKey); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + + nsCOMPtr<nsIMsgFolder> rootFolder; + // use GetRootFolder, because that returns the real + // root, not the deferred to root. + rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) + { + // if isDeferred state has changed, send notification + if (aAccountKey.IsEmpty() != deferredToAccount.IsEmpty()) + { + nsCOMPtr <nsIAtom> deferAtom = MsgGetAtom("isDeferred"); + nsCOMPtr <nsIAtom> canFileAtom = MsgGetAtom("CanFileMessages"); + folderListenerManager->OnItemBoolPropertyChanged(rootFolder, deferAtom, + !deferredToAccount.IsEmpty(), deferredToAccount.IsEmpty()); + folderListenerManager->OnItemBoolPropertyChanged(rootFolder, canFileAtom, + deferredToAccount.IsEmpty(), !deferredToAccount.IsEmpty()); + + // this hack causes the account manager ds to send notifications to the + // xul content builder that make the changed acct appear or disappear + // from the folder pane and related menus. + nsCOMPtr<nsIMsgAccountManager> acctMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID); + if (acctMgr) + { + acctMgr->NotifyServerUnloaded(this); + acctMgr->NotifyServerLoaded(this); + // check if this newly deferred to account is the local folders account + // and needs to have a newly created INBOX. + if (!aAccountKey.IsEmpty()) + { + nsCOMPtr <nsIMsgAccount> account; + acctMgr->GetAccount(aAccountKey, getter_AddRefs(account)); + if (account) + { + nsCOMPtr <nsIMsgIncomingServer> server; + account->GetIncomingServer(getter_AddRefs(server)); + if (server) + { + nsCOMPtr <nsILocalMailIncomingServer> incomingLocalServer = do_QueryInterface(server); + if (incomingLocalServer) + { + nsCOMPtr <nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + // this will fail if it already exists, which is fine. + rootFolder->CreateSubfolder(NS_LITERAL_STRING("Inbox"), nullptr); + } + } + } + } + } + } + } + } + return rv; +} + +//NS_IMPL_GETSET(nsPop3IncomingServer, Authenticated, bool, m_authenticated); + +NS_IMETHODIMP nsPop3IncomingServer::GetAuthenticated(bool *aAuthenticated) +{ + NS_ENSURE_ARG_POINTER(aAuthenticated); + *aAuthenticated = m_authenticated; + return NS_OK; +} + +NS_IMETHODIMP nsPop3IncomingServer::SetAuthenticated(bool aAuthenticated) +{ + m_authenticated = aAuthenticated; + return NS_OK; +} + +nsresult +nsPop3IncomingServer::GetPop3CapabilityFlags(uint32_t *flags) +{ + *flags = m_capabilityFlags; + return NS_OK; +} + +nsresult +nsPop3IncomingServer::SetPop3CapabilityFlags(uint32_t flags) +{ + m_capabilityFlags = flags; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::GetRootMsgFolder(nsIMsgFolder **aRootMsgFolder) +{ + NS_ENSURE_ARG_POINTER(aRootMsgFolder); + nsresult rv = NS_OK; + if (!m_rootMsgFolder) + { + nsCString deferredToAccount; + GetDeferredToAccount(deferredToAccount); + if (deferredToAccount.IsEmpty()) + { + rv = CreateRootFolder(); + m_rootMsgFolder = m_rootFolder; + } + else + { + nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr <nsIMsgAccount> account; + rv = accountManager->GetAccount(deferredToAccount, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv,rv); + if (account) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = account->GetIncomingServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv,rv); + // make sure we're not deferred to ourself... + if (incomingServer && incomingServer != this) + rv = incomingServer->GetRootMsgFolder(getter_AddRefs(m_rootMsgFolder)); + else + rv = NS_ERROR_FAILURE; + } + } + } + + NS_IF_ADDREF(*aRootMsgFolder = m_rootMsgFolder); + return m_rootMsgFolder ? rv : NS_ERROR_FAILURE; +} + +nsresult nsPop3IncomingServer::GetInbox(nsIMsgWindow *msgWindow, nsIMsgFolder **inbox) +{ + NS_ENSURE_ARG_POINTER(inbox); + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = GetRootMsgFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, inbox); + } + + nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(*inbox, &rv); + if (NS_SUCCEEDED(rv) && localInbox) + { + nsCOMPtr <nsIMsgDatabase> db; + rv = (*inbox)->GetMsgDatabase(getter_AddRefs(db)); + if (NS_FAILED(rv)) + { + (*inbox)->SetMsgDatabase(nullptr); + (void) localInbox->SetCheckForNewMessagesAfterParsing(true); + // this will cause a reparse of the mail folder. + localInbox->GetDatabaseWithReparse(nullptr, msgWindow, getter_AddRefs(db)); + rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + } + return rv; +} + +NS_IMETHODIMP nsPop3IncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(kCPop3ServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> inbox; + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + nsCOMPtr<nsIUrlListener> urlListener; + rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_TRUE(rootMsgFolder, NS_ERROR_FAILURE); + + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + if (!inbox) + return NS_ERROR_FAILURE; + + nsCOMPtr <nsIMsgIncomingServer> server; + inbox->GetServer(getter_AddRefs(server)); + + server->SetPerformingBiff(true); + + urlListener = do_QueryInterface(inbox); + + bool downloadOnBiff = false; + rv = GetDownloadOnBiff(&downloadOnBiff); + if (downloadOnBiff) + { + nsCOMPtr <nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv); + if (localInbox && NS_SUCCEEDED(rv)) + { + bool valid = false; + nsCOMPtr <nsIMsgDatabase> db; + rv = inbox->GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + rv = db->GetSummaryValid(&valid); + if (NS_SUCCEEDED(rv) && valid) + rv = pop3Service->GetNewMail(aMsgWindow, urlListener, inbox, this, nullptr); + else + { + bool isLocked; + inbox->GetLocked(&isLocked); + if (!isLocked) + rv = localInbox->GetDatabaseWithReparse(urlListener, aMsgWindow, getter_AddRefs(db)); + if (NS_SUCCEEDED(rv)) + rv = localInbox->SetCheckForNewMessagesAfterParsing(true); + } + } + } + else + rv = pop3Service->CheckForNewMail(aMsgWindow, urlListener, inbox, this, nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::SetFlagsOnDefaultMailboxes() +{ + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(rootFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // pop3 gets an inbox, but no queue (unsent messages) + localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse & + ~nsMsgFolderFlags::Queue); + return NS_OK; +} + + +NS_IMETHODIMP nsPop3IncomingServer::CreateDefaultMailboxes() +{ + nsresult rv = CreateLocalFolder(NS_LITERAL_STRING("Inbox")); + NS_ENSURE_SUCCESS(rv, rv); + + return CreateLocalFolder(NS_LITERAL_STRING("Trash")); +} + +// override this so we can say that deferred accounts can't have messages +// filed to them, which will remove them as targets of all the move/copy +// menu items. +NS_IMETHODIMP +nsPop3IncomingServer::GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer) +{ + NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer); + + nsCString deferredToAccount; + GetDeferredToAccount(deferredToAccount); + *aCanFileMessagesOnServer = deferredToAccount.IsEmpty(); + return NS_OK; +} + + +NS_IMETHODIMP +nsPop3IncomingServer::GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer) +{ + NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer); + + nsCString deferredToAccount; + GetDeferredToAccount(deferredToAccount); + *aCanCreateFoldersOnServer = deferredToAccount.IsEmpty(); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::VerifyLogon(nsIUrlListener *aUrlListener, + nsIMsgWindow *aMsgWindow, nsIURI **aURL) +{ + nsresult rv; + nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + return pop3Service->VerifyLogon(this, aUrlListener, aMsgWindow, aURL); +} + +NS_IMETHODIMP nsPop3IncomingServer::DownloadMailFromServers(nsIPop3IncomingServer** aServers, + uint32_t aCount, + nsIMsgWindow *aMsgWindow, + nsIMsgFolder *aFolder, + nsIUrlListener *aUrlListener) +{ + nsPop3GetMailChainer *getMailChainer = new nsPop3GetMailChainer; + NS_ENSURE_TRUE(getMailChainer, NS_ERROR_OUT_OF_MEMORY); + getMailChainer->AddRef(); // this object owns itself and releases when done. + return getMailChainer->GetNewMailForServers(aServers, aCount, aMsgWindow, aFolder, aUrlListener); +} + +NS_IMETHODIMP nsPop3IncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener, + nsIMsgFolder *aInbox, + nsIURI **aResult) +{ + nsresult rv; + nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + return pop3Service->GetNewMail(aMsgWindow, aUrlListener, aInbox, this, aResult); +} + +// user has clicked get new messages on this server. If other servers defer to this server, +// we need to get new mail for them. But if this server defers to an other server, +// I think we only get new mail for this server. +NS_IMETHODIMP +nsPop3IncomingServer::GetNewMessages(nsIMsgFolder *aFolder, nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener) +{ + nsresult rv; + + nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIMsgFolder> inbox; + rv = GetInbox(aMsgWindow, getter_AddRefs(inbox)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIURI> url; + nsCOMPtr <nsIMsgIncomingServer> server; + nsCOMArray<nsIPop3IncomingServer> deferredServers; + nsCString deferredToAccount; + GetDeferredToAccount(deferredToAccount); + + if (deferredToAccount.IsEmpty()) + { + aFolder->GetServer(getter_AddRefs(server)); + GetDeferredServers(server, deferredServers); + } + if (deferredToAccount.IsEmpty() && !deferredServers.IsEmpty()) + { + nsPop3GetMailChainer *getMailChainer = new nsPop3GetMailChainer; + NS_ENSURE_TRUE(getMailChainer, NS_ERROR_OUT_OF_MEMORY); + getMailChainer->AddRef(); // this object owns itself and releases when done. + deferredServers.InsertElementAt(0, this); + return getMailChainer->GetNewMailForServers(deferredServers.Elements(), + deferredServers.Length(), aMsgWindow, inbox, aUrlListener); + } + if (m_runningProtocol) + return NS_MSG_FOLDER_BUSY; + + return pop3Service->GetNewMail(aMsgWindow, aUrlListener, inbox, this, getter_AddRefs(url)); +} + +NS_IMETHODIMP +nsPop3IncomingServer::GetDownloadMessagesAtStartup(bool *getMessagesAtStartup) +{ + NS_ENSURE_ARG_POINTER(getMessagesAtStartup); + // GetMessages is not automatically done for pop servers at startup. + // We need to trigger that action. Return true. + *getMessagesAtStartup = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::GetCanBeDefaultServer(bool *canBeDefaultServer) +{ + NS_ENSURE_ARG_POINTER(canBeDefaultServer); + *canBeDefaultServer = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::GetCanSearchMessages(bool *canSearchMessages) +{ + // this will return false if this server is deferred, which is what we want. + return GetCanFileMessagesOnServer(canSearchMessages); +} + +NS_IMETHODIMP +nsPop3IncomingServer::CloseCachedConnections() +{ + nsCOMPtr<nsIRequest> channel = do_QueryInterface(m_runningProtocol); + if (channel) + channel->Cancel(NS_ERROR_ABORT); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel) +{ + NS_ENSURE_ARG_POINTER(aSupportLevel); + + nsresult rv; + rv = GetIntValue("offline_support_level", aSupportLevel); + if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv; + + // set default value + *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3IncomingServer::SetRunningProtocol(nsIPop3Protocol *aProtocol) +{ + NS_ASSERTION(!aProtocol || !m_runningProtocol, "overriding running protocol"); + m_runningProtocol = aProtocol; + return NS_OK; +} + +NS_IMETHODIMP nsPop3IncomingServer::GetRunningProtocol(nsIPop3Protocol **aProtocol) +{ + NS_ENSURE_ARG_POINTER(aProtocol); + NS_IF_ADDREF(*aProtocol = m_runningProtocol); + return NS_OK; +} + +NS_IMETHODIMP nsPop3IncomingServer::AddUidlToMark(const char *aUidl, int32_t aMark) +{ + NS_ENSURE_ARG_POINTER(aUidl); + + Pop3UidlEntry *uidlEntry = PR_NEWZAP(Pop3UidlEntry); + NS_ENSURE_TRUE(uidlEntry, NS_ERROR_OUT_OF_MEMORY); + + uidlEntry->uidl = strdup(aUidl); + if (MOZ_UNLIKELY(!uidlEntry->uidl)) { + PR_Free(uidlEntry); + return NS_ERROR_OUT_OF_MEMORY; + } + + uidlEntry->status = (aMark == POP3_DELETE) ? DELETE_CHAR : + (aMark == POP3_FETCH_BODY) ? FETCH_BODY : KEEP; + m_uidlsToMark.AppendElement(uidlEntry); + return NS_OK; +} + +NS_IMETHODIMP nsPop3IncomingServer::MarkMessages() +{ + nsresult rv; + if (m_runningProtocol) + rv = m_runningProtocol->MarkMessages(&m_uidlsToMark); + else + { + nsCString hostName; + nsCString userName; + nsCOMPtr<nsIFile> localPath; + + GetLocalPath(getter_AddRefs(localPath)); + + GetHostName(hostName); + GetUsername(userName); + // do it all in one fell swoop + rv = nsPop3Protocol::MarkMsgForHost(hostName.get(), userName.get(), localPath, m_uidlsToMark); + } + uint32_t count = m_uidlsToMark.Length(); + for (uint32_t i = 0; i < count; i++) + { + Pop3UidlEntry *ue = m_uidlsToMark[i]; + PR_Free(ue->uidl); + PR_Free(ue); + } + m_uidlsToMark.Clear(); + return rv; +} + +NS_IMPL_ISUPPORTS(nsPop3GetMailChainer, nsIUrlListener) + +nsPop3GetMailChainer::nsPop3GetMailChainer() +{ +} +nsPop3GetMailChainer::~nsPop3GetMailChainer() +{ +} + +nsresult nsPop3GetMailChainer::GetNewMailForServers(nsIPop3IncomingServer** servers, + uint32_t count, nsIMsgWindow *msgWindow, + nsIMsgFolder *folderToDownloadTo, nsIUrlListener *listener) +{ + NS_ENSURE_ARG_POINTER(folderToDownloadTo); + + m_serversToGetNewMailFor.AppendElements(servers, count); + m_folderToDownloadTo = folderToDownloadTo; + m_downloadingMsgWindow = msgWindow; + m_listener = listener; + nsCOMPtr <nsIMsgDatabase> destFolderDB; + + nsresult rv = folderToDownloadTo->GetMsgDatabase(getter_AddRefs(destFolderDB)); + if (NS_FAILED(rv) || !destFolderDB) + { + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folderToDownloadTo); + if (localFolder) + { + localFolder->GetDatabaseWithReparse(this, msgWindow, getter_AddRefs(destFolderDB)); + return NS_OK; + } + } + return RunNextGetNewMail(); +} + +NS_IMETHODIMP +nsPop3GetMailChainer::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPop3GetMailChainer::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) +{ + return RunNextGetNewMail(); +} + +nsresult nsPop3GetMailChainer::RunNextGetNewMail() +{ + nsresult rv; + uint32_t numServersLeft = m_serversToGetNewMailFor.Count(); + + for (; numServersLeft > 0;) + { + nsCOMPtr<nsIPop3IncomingServer> popServer(m_serversToGetNewMailFor[0]); + m_serversToGetNewMailFor.RemoveObjectAt(0); + numServersLeft--; + if (popServer) + { + bool deferGetNewMail = false; + nsCOMPtr <nsIMsgIncomingServer> downloadingToServer; + m_folderToDownloadTo->GetServer(getter_AddRefs(downloadingToServer)); + popServer->GetDeferGetNewMail(&deferGetNewMail); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(popServer); + nsCOMPtr<nsIPop3Protocol> protocol; + popServer->GetRunningProtocol(getter_AddRefs(protocol)); + if ((deferGetNewMail || downloadingToServer == server) && !protocol) + { + // have to call routine that just gets mail for one server, + // and ignores deferred servers... + if (server) + { + nsCOMPtr <nsIURI> url; + nsCOMPtr<nsIPop3Service> pop3Service = do_GetService(kCPop3ServiceCID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + return pop3Service->GetNewMail(m_downloadingMsgWindow, this, m_folderToDownloadTo, popServer, getter_AddRefs(url)); + } + } + } + } + rv = m_listener ? m_listener->OnStopRunningUrl(nullptr, NS_OK) : NS_OK; + Release(); // release ref to ourself. + return rv; +} diff --git a/mailnews/local/src/nsPop3IncomingServer.h b/mailnews/local/src/nsPop3IncomingServer.h new file mode 100644 index 000000000..3c98f3bd2 --- /dev/null +++ b/mailnews/local/src/nsPop3IncomingServer.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef __nsPop3IncomingServer_h +#define __nsPop3IncomingServer_h + +#include "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsIPop3IncomingServer.h" +#include "nsILocalMailIncomingServer.h" +#include "nsMsgIncomingServer.h" +#include "nsIPop3Protocol.h" +#include "nsIMsgWindow.h" +#include "nsMailboxServer.h" + +/* get some implementation from nsMsgIncomingServer */ +class nsPop3IncomingServer : public nsMailboxServer, + public nsIPop3IncomingServer, + public nsILocalMailIncomingServer + +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPOP3INCOMINGSERVER + NS_DECL_NSILOCALMAILINCOMINGSERVER + + nsPop3IncomingServer(); + + NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override; + NS_IMETHOD GetDownloadMessagesAtStartup(bool *getMessages) override; + NS_IMETHOD GetCanBeDefaultServer(bool *canBeDefaultServer) override; + NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override; + NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override; + NS_IMETHOD CloseCachedConnections() override; + NS_IMETHOD GetRootMsgFolder(nsIMsgFolder **aRootMsgFolder) override; + NS_IMETHOD GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer) override; + NS_IMETHOD GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer) override; + NS_IMETHOD VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow, + nsIURI **aURL) override; + NS_IMETHOD GetNewMessages(nsIMsgFolder *aFolder, nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener) override; + +protected: + virtual ~nsPop3IncomingServer(); + nsresult GetInbox(nsIMsgWindow *msgWindow, nsIMsgFolder **inbox); + +private: + uint32_t m_capabilityFlags; + bool m_authenticated; + nsCOMPtr <nsIPop3Protocol> m_runningProtocol; + nsCOMPtr <nsIMsgFolder> m_rootMsgFolder; + nsTArray<Pop3UidlEntry*> m_uidlsToMark; +}; + +#endif diff --git a/mailnews/local/src/nsPop3Protocol.cpp b/mailnews/local/src/nsPop3Protocol.cpp new file mode 100644 index 000000000..825f45ab3 --- /dev/null +++ b/mailnews/local/src/nsPop3Protocol.cpp @@ -0,0 +1,4176 @@ +/* -*- 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/. + * + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Jason Eager <jce2@po.cwru.edu> + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + * 06/07/2000 Jason Eager Added check for out of disk space + */ + +#include "nscore.h" +#include "msgCore.h" // precompiled header... +#include "nsNetUtil.h" +#include "nspr.h" +#include "plbase64.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsISafeOutputStream.h" +#include "nsPop3Protocol.h" +#include "MailNewsTypes.h" +#include "nsStringGlue.h" +#include "nsIPrompt.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgPluggableStore.h" +#include "nsTextFormatter.h" +#include "nsCOMPtr.h" +#include "nsIMsgWindow.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsIMsgLocalMailFolder.h" +#include "nsIDocShell.h" +#include "nsMsgUtils.h" +#include "nsISocketTransport.h" +#include "nsISSLSocketControl.h" +#include "nsILineInputStream.h" +#include "nsIInterfaceRequestor.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgBaseCID.h" +#include "nsIProxyInfo.h" +#include "nsCRT.h" +#include "mozilla/Services.h" +#include "mozilla/Logging.h" +#include "mozilla/Attributes.h" + +using namespace mozilla; + +PRLogModuleInfo *POP3LOGMODULE = nullptr; +#define POP3LOG(str) "%s: [this=%p] " str, POP3LOGMODULE->name, this + +static int +net_pop3_remove_messages_marked_delete(PLHashEntry* he, + int msgindex, + void *arg) +{ + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + return (uidlEntry->status == DELETE_CHAR) + ? HT_ENUMERATE_REMOVE : HT_ENUMERATE_NEXT; +} + +uint32_t TimeInSecondsFromPRTime(PRTime prTime) +{ + return (uint32_t)(prTime / PR_USEC_PER_SEC); +} + +static void +put_hash(PLHashTable* table, const char* key, char value, uint32_t dateReceived) +{ + // don't put not used slots or empty uid into hash + if (key && *key) + { + Pop3UidlEntry* tmp = PR_NEWZAP(Pop3UidlEntry); + if (tmp) + { + tmp->uidl = PL_strdup(key); + if (tmp->uidl) + { + tmp->dateReceived = dateReceived; + tmp->status = value; + PL_HashTableAdd(table, (const void *)tmp->uidl, (void*) tmp); + } + else + PR_Free(tmp); + } + } +} + +static int +net_pop3_copy_hash_entries(PLHashEntry* he, int msgindex, void *arg) +{ + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + put_hash((PLHashTable *) arg, uidlEntry->uidl, uidlEntry->status, uidlEntry->dateReceived); + return HT_ENUMERATE_NEXT; +} + +static void * +AllocUidlTable(void * /* pool */, size_t size) +{ + return PR_MALLOC(size); +} + +static void +FreeUidlTable(void * /* pool */, void *item) +{ + PR_Free(item); +} + +static PLHashEntry * +AllocUidlInfo(void *pool, const void *key) +{ + return PR_NEWZAP(PLHashEntry); +} + +static void +FreeUidlInfo(void * /* pool */, PLHashEntry *he, unsigned flag) +{ + if (flag == HT_FREE_ENTRY) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + if (uidlEntry) + { + PR_Free(uidlEntry->uidl); + PR_Free(uidlEntry); + } + PR_Free(he); + } +} + +static PLHashAllocOps gHashAllocOps = { + AllocUidlTable, FreeUidlTable, + AllocUidlInfo, FreeUidlInfo +}; + + +static Pop3UidlHost* +net_pop3_load_state(const char* searchhost, + const char* searchuser, + nsIFile *mailDirectory) +{ + Pop3UidlHost* result = nullptr; + Pop3UidlHost* current = nullptr; + Pop3UidlHost* tmp; + + result = PR_NEWZAP(Pop3UidlHost); + if (!result) + return nullptr; + result->host = PL_strdup(searchhost); + result->user = PL_strdup(searchuser); + result->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr); + + if (!result->host || !result->user || !result->hash) + { + PR_Free(result->host); + PR_Free(result->user); + if (result->hash) + PL_HashTableDestroy(result->hash); + PR_Free(result); + return nullptr; + } + + nsCOMPtr <nsIFile> popState; + mailDirectory->Clone(getter_AddRefs(popState)); + if (!popState) + return nullptr; + popState->AppendNative(NS_LITERAL_CSTRING("popstate.dat")); + + nsCOMPtr<nsIInputStream> fileStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), popState); + NS_ENSURE_SUCCESS(rv, result); + + nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv)); + NS_ENSURE_SUCCESS(rv, result); + + bool more = true; + nsCString line; + + while (more && NS_SUCCEEDED(rv)) + { + lineInputStream->ReadLine(line, &more); + if (line.IsEmpty()) + continue; + char firstChar = line.CharAt(0); + if (firstChar == '#') + continue; + if (firstChar == '*') { + /* It's a host&user line. */ + current = nullptr; + char *lineBuf = line.BeginWriting() + 1; // ok because we know the line isn't empty + char *host = NS_strtok(" \t\r\n", &lineBuf); + /* without space to also get realnames - see bug 225332 */ + char *user = NS_strtok("\t\r\n", &lineBuf); + if (!host || !user) + continue; + for (tmp = result ; tmp ; tmp = tmp->next) + { + if (!strcmp(host, tmp->host) && !strcmp(user, tmp->user)) + { + current = tmp; + break; + } + } + if (!current) + { + current = PR_NEWZAP(Pop3UidlHost); + if (current) + { + current->host = strdup(host); + current->user = strdup(user); + current->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr); + if (!current->host || !current->user || !current->hash) + { + PR_Free(current->host); + PR_Free(current->user); + if (current->hash) + PL_HashTableDestroy(current->hash); + PR_Free(current); + } + else + { + current->next = result->next; + result->next = current; + } + } + } + } + else + { + /* It's a line with a UIDL on it. */ + if (current) + { + for (int32_t pos = line.FindChar('\t'); pos != -1; pos = line.FindChar('\t', pos)) + line.Replace(pos, 1, ' '); + + nsTArray<nsCString> lineElems; + ParseString(line, ' ', lineElems); + if (lineElems.Length() < 2) + continue; + nsCString *flags = &lineElems[0]; + nsCString *uidl = &lineElems[1]; + uint32_t dateReceived = TimeInSecondsFromPRTime(PR_Now()); // if we don't find a date str, assume now. + if (lineElems.Length() > 2) + dateReceived = atoi(lineElems[2].get()); + if (!flags->IsEmpty() && !uidl->IsEmpty()) + { + char flag = flags->CharAt(0); + if ((flag == KEEP) || (flag == DELETE_CHAR) || + (flag == TOO_BIG) || (flag == FETCH_BODY)) + { + put_hash(current->hash, uidl->get(), flag, dateReceived); + } + else + { + NS_ASSERTION(false, "invalid flag in popstate.dat"); + } + } + } + } + } + fileStream->Close(); + + return result; +} + +static int +hash_clear_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + PR_Free(uidlEntry->uidl); + PR_Free(uidlEntry); + he->value = nullptr; + + return HT_ENUMERATE_REMOVE; +} + +static int +hash_empty_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + *((bool*) arg) = false; + return HT_ENUMERATE_STOP; +} + +static bool +hash_empty(PLHashTable* hash) +{ + bool result = true; + PL_HashTableEnumerateEntries(hash, hash_empty_mapper, (void *)&result); + return result; +} + + +static int +net_pop3_write_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + nsIOutputStream* file = (nsIOutputStream*) arg; + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + NS_ASSERTION((uidlEntry->status == KEEP) || + (uidlEntry->status == DELETE_CHAR) || + (uidlEntry->status == FETCH_BODY) || + (uidlEntry->status == TOO_BIG), "invalid status"); + char* tmpBuffer = PR_smprintf("%c %s %d" MSG_LINEBREAK, uidlEntry->status, (char*) + uidlEntry->uidl, uidlEntry->dateReceived); + PR_ASSERT(tmpBuffer); + uint32_t numBytesWritten; + file->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten); + PR_Free(tmpBuffer); + return HT_ENUMERATE_NEXT; +} + +static int +net_pop3_delete_old_msgs_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + PRTime cutOffDate = (PRTime) arg; + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + if (uidlEntry->dateReceived < cutOffDate) + uidlEntry->status = DELETE_CHAR; // mark for deletion + return HT_ENUMERATE_NEXT; +} + +static void +net_pop3_write_state(Pop3UidlHost* host, nsIFile *mailDirectory) +{ + int32_t len = 0; + nsCOMPtr <nsIFile> popState; + + mailDirectory->Clone(getter_AddRefs(popState)); + if (!popState) + return; + popState->AppendNative(NS_LITERAL_CSTRING("popstate.dat")); + + nsCOMPtr<nsIOutputStream> fileOutputStream; + nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(fileOutputStream), popState, -1, 00600); + if (NS_FAILED(rv)) + return; + + const char tmpBuffer[] = + "# POP3 State File" MSG_LINEBREAK + "# This is a generated file! Do not edit." MSG_LINEBREAK + MSG_LINEBREAK; + + uint32_t numBytesWritten; + fileOutputStream->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten); + + for (; host && (len >= 0); host = host->next) + { + if (!hash_empty(host->hash)) + { + fileOutputStream->Write("*", 1, &numBytesWritten); + fileOutputStream->Write(host->host, strlen(host->host), &numBytesWritten); + fileOutputStream->Write(" ", 1, &numBytesWritten); + fileOutputStream->Write(host->user, strlen(host->user), &numBytesWritten); + fileOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &numBytesWritten); + PL_HashTableEnumerateEntries(host->hash, net_pop3_write_mapper, (void *)fileOutputStream); + } + } + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(fileOutputStream); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save pop state! possible data loss"); + } + } +} + +static void +net_pop3_free_state(Pop3UidlHost* host) +{ + Pop3UidlHost* h; + while (host) + { + h = host->next; + PR_Free(host->host); + PR_Free(host->user); + PL_HashTableDestroy(host->hash); + PR_Free(host); + host = h; + } +} + +/* +Look for a specific UIDL string in our hash tables, if we have it then we need +to mark the message for deletion so that it can be deleted later. If the uidl of the +message is not found, then the message was downloaded completely and already deleted +from the server. So this only applies to messages kept on the server or too big +for download. */ +/* static */ +void nsPop3Protocol::MarkMsgInHashTable(PLHashTable *hashTable, const Pop3UidlEntry *uidlE, bool *changed) +{ + if (uidlE->uidl) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(hashTable, uidlE->uidl); + if (uidlEntry) + { + if (uidlEntry->status != uidlE->status) + { + uidlEntry->status = uidlE->status; + *changed = true; + } + } + } +} + +/* static */ +nsresult +nsPop3Protocol::MarkMsgForHost(const char *hostName, const char *userName, + nsIFile *mailDirectory, + nsTArray<Pop3UidlEntry*> &UIDLArray) +{ + if (!hostName || !userName || !mailDirectory) + return NS_ERROR_NULL_POINTER; + + Pop3UidlHost *uidlHost = net_pop3_load_state(hostName, userName, mailDirectory); + if (!uidlHost) + return NS_ERROR_OUT_OF_MEMORY; + + bool changed = false; + + uint32_t count = UIDLArray.Length(); + for (uint32_t i = 0; i < count; i++) + { + MarkMsgInHashTable(uidlHost->hash, UIDLArray[i], &changed); + } + + if (changed) + net_pop3_write_state(uidlHost, mailDirectory); + net_pop3_free_state(uidlHost); + return NS_OK; +} + + + +NS_IMPL_ADDREF_INHERITED(nsPop3Protocol, nsMsgProtocol) +NS_IMPL_RELEASE_INHERITED(nsPop3Protocol, nsMsgProtocol) + + + +NS_INTERFACE_MAP_BEGIN(nsPop3Protocol) + NS_INTERFACE_MAP_ENTRY(nsIPop3Protocol) + NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener) +NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol) + +// nsPop3Protocol class implementation + +nsPop3Protocol::nsPop3Protocol(nsIURI* aURL) +: nsMsgProtocol(aURL), + m_bytesInMsgReceived(0), + m_totalFolderSize(0), + m_totalDownloadSize(0), + m_totalBytesReceived(0), + m_lineStreamBuffer(nullptr), + m_pop3ConData(nullptr) +{ +} + +nsresult nsPop3Protocol::Initialize(nsIURI * aURL) +{ + nsresult rv = NS_OK; + if (!POP3LOGMODULE) + POP3LOGMODULE = PR_NewLogModule("POP3"); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Initialize()"))); + + m_pop3ConData = (Pop3ConData *)PR_NEWZAP(Pop3ConData); + if(!m_pop3ConData) + return NS_ERROR_OUT_OF_MEMORY; + + m_totalBytesReceived = 0; + m_bytesInMsgReceived = 0; + m_totalFolderSize = 0; + m_totalDownloadSize = 0; + m_totalBytesReceived = 0; + m_tlsEnabled = false; + m_socketType = nsMsgSocketType::trySTARTTLS; + m_prefAuthMethods = POP3_AUTH_MECH_UNDEFINED; + m_failedAuthMethods = 0; + m_password_already_sent = false; + m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED; + m_needToRerunUrl = false; + + if (aURL) + { + // extract out message feedback if there is any. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL); + if (mailnewsUrl) + { + nsCOMPtr<nsIMsgIncomingServer> server; + mailnewsUrl->GetServer(getter_AddRefs(server)); + NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER); + + rv = server->GetSocketType(&m_socketType); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t authMethod = 0; + rv = server->GetAuthMethod(&authMethod); + NS_ENSURE_SUCCESS(rv,rv); + InitPrefAuthMethods(authMethod); + + m_pop3Server = do_QueryInterface(server); + if (m_pop3Server) + m_pop3Server->GetPop3CapabilityFlags(&m_pop3ConData->capability_flags); + } + + m_url = do_QueryInterface(aURL); + + // When we are making a secure connection, we need to make sure that we + // pass an interface requestor down to the socket transport so that PSM can + // retrieve a nsIPrompt instance if needed. + nsCOMPtr<nsIInterfaceRequestor> ir; + if (m_socketType != nsMsgSocketType::plain) + { + nsCOMPtr<nsIMsgWindow> msgwin; + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgwin)); + if (!msgwin) + GetTopmostMsgWindow(getter_AddRefs(msgwin)); + if (msgwin) + { + nsCOMPtr<nsIDocShell> docshell; + msgwin->GetRootDocShell(getter_AddRefs(docshell)); + ir = do_QueryInterface(docshell); + nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; + msgwin->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); + if (notificationCallbacks) + { + nsCOMPtr<nsIInterfaceRequestor> aggregrateIR; + MsgNewInterfaceRequestorAggregation(notificationCallbacks, ir, getter_AddRefs(aggregrateIR)); + ir = aggregrateIR; + } + } + } + + int32_t port = 0; + nsCString hostName; + aURL->GetPort(&port); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + server->GetRealHostName(hostName); + + nsCOMPtr<nsIProxyInfo> proxyInfo; + rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo)); + if (NS_FAILED(rv)) proxyInfo = nullptr; + + const char *connectionType = nullptr; + if (m_socketType == nsMsgSocketType::SSL) + connectionType = "ssl"; + else if (m_socketType == nsMsgSocketType::trySTARTTLS || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + connectionType = "starttls"; + + rv = OpenNetworkSocketWithInfo(hostName.get(), port, connectionType, proxyInfo, ir); + if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) + { + m_socketType = nsMsgSocketType::plain; + rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, proxyInfo, ir); + } + + if(NS_FAILED(rv)) + return rv; + } // if we got a url... + + m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true); + if(!m_lineStreamBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + return bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(mLocalBundle)); +} + +nsPop3Protocol::~nsPop3Protocol() +{ + Cleanup(); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("~nsPop3Protocol()"))); +} + +void nsPop3Protocol::Cleanup() +{ + if (m_pop3ConData->newuidl) + { + PL_HashTableDestroy(m_pop3ConData->newuidl); + m_pop3ConData->newuidl = nullptr; + } + + net_pop3_free_state(m_pop3ConData->uidlinfo); + + FreeMsgInfo(); + PR_Free(m_pop3ConData->only_uidl); + PR_Free(m_pop3ConData); + + delete m_lineStreamBuffer; + m_lineStreamBuffer = nullptr; +} + +void nsPop3Protocol::SetCapFlag(uint32_t flag) +{ + m_pop3ConData->capability_flags |= flag; +} + +void nsPop3Protocol::ClearCapFlag(uint32_t flag) +{ + m_pop3ConData->capability_flags &= ~flag; +} + +bool nsPop3Protocol::TestCapFlag(uint32_t flag) +{ + return m_pop3ConData->capability_flags & flag; +} + +uint32_t nsPop3Protocol::GetCapFlags() +{ + return m_pop3ConData->capability_flags; +} + +nsresult nsPop3Protocol::FormatCounterString(const nsString &stringName, + uint32_t count1, + uint32_t count2, + nsString &resultString) +{ + nsAutoString count1String; + count1String.AppendInt(count1); + + nsAutoString count2String; + count2String.AppendInt(count2); + + const char16_t *formatStrings[] = { + count1String.get(), + count2String.get() + }; + + return mLocalBundle->FormatStringFromName(stringName.get(), + formatStrings, 2, + getter_Copies(resultString)); +} + +void nsPop3Protocol::UpdateStatus(const char16_t *aStatusName) +{ + nsString statusMessage; + mLocalBundle->GetStringFromName(aStatusName, + getter_Copies(statusMessage)); + UpdateStatusWithString(statusMessage.get()); +} + +void nsPop3Protocol::UpdateStatusWithString(const char16_t *aStatusString) +{ + if (mProgressEventSink) + { + mozilla::DebugOnly<nsresult> rv = + mProgressEventSink->OnStatus(this, m_channelContext, + NS_OK, aStatusString); // XXX i18n message + NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result"); + } +} + +void nsPop3Protocol::UpdateProgressPercent(int64_t totalDone, int64_t total) +{ + if (mProgressEventSink) + mProgressEventSink->OnProgress(this, m_channelContext, totalDone, total); +} + +// note: SetUsername() expects an unescaped string +// do not pass in an escaped string +void nsPop3Protocol::SetUsername(const char* name) +{ + NS_ASSERTION(name, "no name specified!"); + if (name) + m_username = name; +} + +nsresult nsPop3Protocol::RerunUrl() +{ + nsCOMPtr<nsIURI> url = do_QueryInterface(m_url); + ClearFlag(POP3_PASSWORD_FAILED); + m_pop3Server->SetRunningProtocol(nullptr); + Cleanup(); + return LoadUrl(url, nullptr); +} + +Pop3StatesEnum nsPop3Protocol::GetNextPasswordObtainState() +{ + switch (m_pop3ConData->next_state) + { + case POP3_OBTAIN_PASSWORD_EARLY: + return POP3_FINISH_OBTAIN_PASSWORD_EARLY; + case POP3_SEND_USERNAME: + case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME: + return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME; + case POP3_SEND_PASSWORD: + case POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD: + return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD; + default: + // Should never get here. + NS_NOTREACHED("Invalid next_state in GetNextPasswordObtainState"); + } + return POP3_ERROR_DONE; +} + +nsresult nsPop3Protocol::StartGetAsyncPassword(Pop3StatesEnum aNextState) +{ + nsresult rv; + + // Try and avoid going async if possible - if we haven't got into a password + // failure state and the server has a password stored for this session, then + // use it. + if (!TestFlag(POP3_PASSWORD_FAILED)) + { + nsCOMPtr<nsIMsgIncomingServer> server = + do_QueryInterface(m_pop3Server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = server->GetPassword(m_passwordResult); + if (NS_SUCCEEDED(rv) && !m_passwordResult.IsEmpty()) + { + m_pop3ConData->next_state = GetNextPasswordObtainState(); + return NS_OK; + } + } + + // We're now going to need to do something that will end up with us either + // poking the login manger or prompting the user. We need to ensure we only + // do one prompt at a time (and loging manager could cause a master password + // prompt), so we need to use the async prompter. + nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter = + do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + m_pop3ConData->next_state = aNextState; + + // Although we're not actually pausing for a read, we'll do so anyway to let + // the async prompt run. Once it is our turn again we'll call back into + // ProcessProtocolState. + m_pop3ConData->pause_for_read = true; + + nsCString server("unknown"); + m_url->GetPrePath(server); + + rv = asyncPrompter->QueueAsyncAuthPrompt(server, false, this); + // Explict NS_ENSURE_SUCCESS for debug purposes as errors tend to get + // hidden. + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP nsPop3Protocol::OnPromptStart(bool *aResult) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("OnPromptStart()"))); + + *aResult = false; + + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString passwordResult; + + // pass the failed password into the password prompt so that + // it will be pre-filled, in case it failed because of a + // server problem and not because it was wrong. + if (!m_lastPasswordSent.IsEmpty()) + passwordResult = m_lastPasswordSent; + + // Set up some items that we're going to need for the prompting. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (mailnewsUrl) + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + + nsCString userName; + server->GetRealUsername(userName); + + nsCString hostName; + server->GetRealHostName(hostName); + + nsString passwordPrompt; + NS_ConvertUTF8toUTF16 userNameUTF16(userName); + NS_ConvertUTF8toUTF16 hostNameUTF16(hostName); + const char16_t* passwordParams[] = { userNameUTF16.get(), + hostNameUTF16.get() }; + + // if the last prompt got us a bad password then show a special dialog + if (TestFlag(POP3_PASSWORD_FAILED)) + { + // Biff case (no msgWindow) shouldn't cause prompts or passwords to get forgotten at all + // TODO shouldn't we skip the new password prompt below as well for biff? Just exit here? + if (msgWindow) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, + (POP3LOG("POP: ask user what to do (after password failed): new password, retry or cancel"))); + + int32_t buttonPressed = 0; + if (NS_SUCCEEDED(MsgPromptLoginFailed(msgWindow, hostName, + &buttonPressed))) + { + if (buttonPressed == 1) // Cancel button + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("cancel button pressed"))); + // Abort quickly and stop trying for now. + + // If we haven't actually connected yet (i.e. we're doing an early + // attempt to get the username/password but we've previously failed + // for some reason), then skip straight to POP3_FREE as it isn't an + // error in this connection, and just ends up with us closing the + // socket and saying we've aborted the bind. Otherwise, pretend this + // is an error and move on. + m_pop3ConData->next_state = + m_pop3ConData->next_state == POP3_OBTAIN_PASSWORD_EARLY ? + POP3_FREE : POP3_ERROR_DONE; + + // Clear the password we're going to return to force failure in + // the get mail instance. + passwordResult.Truncate(); + + // We also have to clear the password failed flag, otherwise we'll + // automatically try again. + ClearFlag(POP3_PASSWORD_FAILED); + + // As we're async, calling ProcessProtocolState gets things going + // again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; + } + else if (buttonPressed == 2) // "New password" button + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("new password button pressed"))); + // Forget the stored password + // and we'll prompt for a new one next time around. + rv = server->ForgetPassword(); + NS_ENSURE_SUCCESS(rv, rv); + + // try all methods again with new password + ResetAuthMethods(); + // ... apart from GSSAPI, which doesn't care about passwords + MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI); + if (m_needToRerunUrl) + return RerunUrl(); + } + else if (buttonPressed == 0) // "Retry" button + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("retry button pressed"))); + // try all methods again, including GSSAPI + ResetAuthMethods(); + ClearFlag(POP3_PASSWORD_FAILED|POP3_AUTH_FAILURE); + + if (m_needToRerunUrl) + return RerunUrl(); + + // It is a bit strange that we're going onto the next state that + // would essentially send the password. However in resetting the + // auth methods above, we're setting up SendUsername, SendPassword + // and friends to abort and return to the POP3_SEND_CAPA state. + // Hence we can do this safely. + m_pop3ConData->next_state = GetNextPasswordObtainState(); + // As we're async, calling ProcessProtocolState gets things going + // again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; + } + } + } + mLocalBundle->FormatStringFromName( + u"pop3PreviouslyEnteredPasswordIsInvalidPrompt", + passwordParams, 2, getter_Copies(passwordPrompt)); + } + else + // Otherwise this is the first time we've asked about the server's + // password so show a first time prompt. + mLocalBundle->FormatStringFromName( + u"pop3EnterPasswordPrompt", + passwordParams, 2, getter_Copies(passwordPrompt)); + + nsString passwordTitle; + mLocalBundle->GetStringFromName( + u"pop3EnterPasswordPromptTitle", + getter_Copies(passwordTitle)); + + // Now go and get the password. + if (!passwordPrompt.IsEmpty() && !passwordTitle.IsEmpty()) + rv = server->GetPasswordWithUI(passwordPrompt, passwordTitle, + msgWindow, passwordResult); + ClearFlag(POP3_PASSWORD_FAILED|POP3_AUTH_FAILURE); + + // If it failed or the user cancelled the prompt, just abort the + // connection. + if (NS_FAILED(rv) || + rv == NS_MSG_PASSWORD_PROMPT_CANCELLED) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + m_passwordResult.Truncate(); + *aResult = false; + } + else + { + m_passwordResult = passwordResult; + m_pop3ConData->next_state = GetNextPasswordObtainState(); + *aResult = true; + } + // Because this was done asynchronously, now call back into + // ProcessProtocolState to get the protocol going again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::OnPromptAuthAvailable() +{ + NS_NOTREACHED("Did not expect to get POP3 protocol queuing up auth " + "connections for same server"); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::OnPromptCanceled() +{ + // A prompt was cancelled, so just abort out the connection + m_pop3ConData->next_state = POP3_ERROR_DONE; + // As we're async, calling ProcessProtocolState gets things going again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::OnTransportStatus(nsITransport *aTransport, nsresult aStatus, int64_t aProgress, int64_t aProgressMax) +{ + return nsMsgProtocol::OnTransportStatus(aTransport, aStatus, aProgress, aProgressMax); +} + +// stop binding is a "notification" informing us that the stream associated with aURL is going away. +NS_IMETHODIMP nsPop3Protocol::OnStopRequest(nsIRequest *aRequest, nsISupports * aContext, nsresult aStatus) +{ + // If the server dropped the connection, m_socketIsOpen will be true, before + // we call nsMsgProtocol::OnStopRequest. The call will force a close socket, + // but we still want to go through the state machine one more time to cleanup + // the protocol object. + if (m_socketIsOpen) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_url); + + // Check if the connection was dropped before getting back an auth error. + // If we got the auth error, the next state would be + // POP3_OBTAIN_PASSWORD_EARLY. + if ((m_pop3ConData->next_state_after_response == POP3_NEXT_AUTH_STEP || + m_pop3ConData->next_state_after_response == POP3_AUTH_LOGIN_RESPONSE) && + m_pop3ConData->next_state != POP3_OBTAIN_PASSWORD_EARLY) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("dropped connection before auth error"))); + SetFlag(POP3_AUTH_FAILURE); + m_pop3ConData->command_succeeded = false; + m_needToRerunUrl = true; + m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP; + ProcessProtocolState(nullptr, nullptr, 0, 0); + } + // We can't call nsMsgProtocol::OnStopRequest because it calls SetUrlState, + // which notifies the URLListeners, but we need to do a bit of cleanup + // before running the url again. + CloseSocket(); + if (m_loadGroup) + m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus); + m_pop3ConData->next_state = POP3_ERROR_DONE; + ProcessProtocolState(nullptr, nullptr, 0, 0); + + if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) + nsMsgProtocol::ShowAlertMessage(msgUrl, aStatus); + + return NS_OK; + } + nsresult rv = nsMsgProtocol::OnStopRequest(aRequest, aContext, aStatus); + + // turn off the server busy flag on stop request - we know we're done, right? + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("Clearing server busy in nsPop3Protocol::OnStopRequest"))); + server->SetServerBusy(false); // the server is not busy + } + if(m_pop3ConData->list_done) + CommitState(true); + if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) + Abort(); + return rv; +} + +void nsPop3Protocol::Abort() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Abort"))); + + if(m_pop3ConData->msg_closure) + { + m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr); + m_pop3ConData->msg_closure = nullptr; + } + // need this to close the stream on the inbox. + m_nsIPop3Sink->AbortMailDelivery(this); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("Clearing running protocol in nsPop3Protocol::Abort()"))); + m_pop3Server->SetRunningProtocol(nullptr); +} + +NS_IMETHODIMP nsPop3Protocol::Cancel(nsresult status) // handle stop button +{ + Abort(); + return nsMsgProtocol::Cancel(NS_BINDING_ABORTED); +} + + +nsresult nsPop3Protocol::LoadUrl(nsIURI* aURL, nsISupports * /* aConsumer */) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LoadUrl()"))); + + nsresult rv = Initialize(aURL); + NS_ENSURE_SUCCESS(rv, rv); + if (aURL) + m_url = do_QueryInterface(aURL); + else + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURL> url = do_QueryInterface(aURL, &rv); + if (NS_FAILED(rv)) return rv; + + int32_t port; + rv = url->GetPort(&port); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_CheckPortSafety(port, "pop"); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString queryPart; + rv = url->GetQuery(queryPart); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get the url spect"); + + m_pop3ConData->only_check_for_new_mail = (PL_strcasestr(queryPart.get(), "check") != nullptr); + m_pop3ConData->verify_logon = (PL_strcasestr(queryPart.get(), "verifyLogon") != nullptr); + m_pop3ConData->get_url = (PL_strcasestr(queryPart.get(), "gurl") != nullptr); + + bool deleteByAgeFromServer = false; + int32_t numDaysToLeaveOnServer = -1; + if (!m_pop3ConData->verify_logon) + { + // Pick up pref setting regarding leave messages on server, message size limit + + m_pop3Server->GetLeaveMessagesOnServer(&m_pop3ConData->leave_on_server); + m_pop3Server->GetHeadersOnly(&m_pop3ConData->headers_only); + bool limitMessageSize = false; + + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + // size limits are superseded by headers_only mode + if (!m_pop3ConData->headers_only) + { + server->GetLimitOfflineMessageSize(&limitMessageSize); + if (limitMessageSize) + { + int32_t max_size = 0; // default size + server->GetMaxMessageSize(&max_size); + m_pop3ConData->size_limit = (max_size) ? max_size * 1024 : 50 * 1024; + } + } + m_pop3Server->GetDeleteByAgeFromServer(&deleteByAgeFromServer); + if (deleteByAgeFromServer) + m_pop3Server->GetNumDaysToLeaveOnServer(&numDaysToLeaveOnServer); + } + } + + // UIDL stuff + nsCOMPtr<nsIPop3URL> pop3Url = do_QueryInterface(m_url); + if (pop3Url) + pop3Url->GetPop3Sink(getter_AddRefs(m_nsIPop3Sink)); + + nsCOMPtr<nsIFile> mailDirectory; + + nsCString hostName; + nsCString userName; + + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + rv = server->GetLocalPath(getter_AddRefs(mailDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + server->SetServerBusy(true); // the server is now busy + server->GetHostName(hostName); + server->GetUsername(userName); + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, + (POP3LOG("Connecting to server %s:%d"), hostName.get(), port)); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("Setting server busy in nsPop3Protocol::LoadUrl()"))); + } + + if (!m_pop3ConData->verify_logon) + m_pop3ConData->uidlinfo = net_pop3_load_state(hostName.get(), userName.get(), mailDirectory); + + m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NoMail; + + if (m_pop3ConData->uidlinfo && numDaysToLeaveOnServer > 0) + { + uint32_t nowInSeconds = TimeInSecondsFromPRTime(PR_Now()); + uint32_t cutOffDay = nowInSeconds - (60 * 60 * 24 * numDaysToLeaveOnServer); + + PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash, net_pop3_delete_old_msgs_mapper, (void *)(uintptr_t) cutOffDay); + } + const char* uidl = PL_strcasestr(queryPart.get(), "uidl="); + PR_FREEIF(m_pop3ConData->only_uidl); + + if (uidl) + { + uidl += 5; + nsCString unescapedData; + MsgUnescapeString(nsDependentCString(uidl), 0, unescapedData); + m_pop3ConData->only_uidl = PL_strdup(unescapedData.get()); + + mSuppressListenerNotifications = true; // suppress on start and on stop because this url won't have any content to display + } + + m_pop3ConData->next_state = POP3_START_CONNECT; + m_pop3ConData->next_state_after_response = POP3_FINISH_CONNECT; + if (NS_SUCCEEDED(rv)) + { + m_pop3Server->SetRunningProtocol(this); + return nsMsgProtocol::LoadUrl(aURL); + } + else + return rv; +} + +void +nsPop3Protocol::FreeMsgInfo() +{ + int i; + if (m_pop3ConData->msg_info) + { + for (i=0 ; i<m_pop3ConData->number_of_messages ; i++) + { + if (m_pop3ConData->msg_info[i].uidl) + PR_Free(m_pop3ConData->msg_info[i].uidl); + m_pop3ConData->msg_info[i].uidl = nullptr; + } + PR_Free(m_pop3ConData->msg_info); + m_pop3ConData->msg_info = nullptr; + } +} + +int32_t +nsPop3Protocol::WaitForStartOfConnectionResponse(nsIInputStream* aInputStream, + uint32_t length) +{ + char * line = nullptr; + uint32_t line_length = 0; + bool pauseForMoreData = false; + nsresult rv; + line = m_lineStreamBuffer->ReadNextLine(aInputStream, line_length, pauseForMoreData, &rv); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + PR_Free(line); + return(line_length); + } + + if(*line == '+') + { + m_pop3ConData->command_succeeded = true; + if(PL_strlen(line) > 4) + m_commandResponse = line + 4; + else + m_commandResponse = line; + + if (m_prefAuthMethods & POP3_HAS_AUTH_APOP) + { + if (NS_SUCCEEDED(GetApopTimestamp())) + SetCapFlag(POP3_HAS_AUTH_APOP); + } + else + ClearCapFlag(POP3_HAS_AUTH_APOP); + + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; /* don't pause */ + } + + PR_Free(line); + return(1); /* everything ok */ +} + +int32_t +nsPop3Protocol::WaitForResponse(nsIInputStream* inputStream, uint32_t length) +{ + char * line; + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + + PR_Free(line); + return(ln); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + if(*line == '+') + { + m_pop3ConData->command_succeeded = true; + if(PL_strlen(line) > 4) + { + if(!PL_strncasecmp(line, "+OK", 3)) + m_commandResponse = line + 4; + else // challenge answer to AUTH CRAM-MD5 and LOGIN username/password + m_commandResponse = line + 2; + } + else + m_commandResponse = line; + } + else + { + m_pop3ConData->command_succeeded = false; + if(PL_strlen(line) > 5) + m_commandResponse = line + 5; + else + m_commandResponse = line; + + // search for the response codes (RFC 2449, chapter 8 and RFC 3206) + if(TestCapFlag(POP3_HAS_RESP_CODES | POP3_HAS_AUTH_RESP_CODE)) + { + // code for authentication failure due to the user's credentials + if(m_commandResponse.Find("[AUTH", true) >= 0) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("setting auth failure"))); + SetFlag(POP3_AUTH_FAILURE); + } + + // codes for failures due to other reasons + if(m_commandResponse.Find("[LOGIN-DELAY", true) >= 0 || + m_commandResponse.Find("[IN-USE", true) >= 0 || + m_commandResponse.Find("[SYS", true) >= 0) + SetFlag(POP3_STOPLOGIN); + + // remove the codes from the response string presented to the user + int32_t i = m_commandResponse.FindChar(']'); + if(i >= 0) + m_commandResponse.Cut(0, i + 2); + } + } + + m_pop3ConData->next_state = m_pop3ConData->next_state_after_response; + m_pop3ConData->pause_for_read = false; /* don't pause */ + + PR_Free(line); + return(1); /* everything ok */ +} + +int32_t +nsPop3Protocol::Error(const char* err_code, + const char16_t **params, + uint32_t length) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("ERROR: %s"), err_code)); + + // the error code is just the resource name for the error string... + // so print out that error message! + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsString accountName; + nsresult rv = server->GetPrettyName(accountName); + NS_ENSURE_SUCCESS(rv, -1); + const char16_t *titleParams[] = { accountName.get() }; + nsString dialogTitle; + mLocalBundle->FormatStringFromName( + u"pop3ErrorDialogTitle", + titleParams, 1, getter_Copies(dialogTitle)); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + // we handle "pop3TmpDownloadError" earlier... + if (strcmp(err_code, "pop3TmpDownloadError") && NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIPrompt> dialog; + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); //it is ok to have null msgWindow, for example when biffing + if (NS_SUCCEEDED(rv) && msgWindow) + { + rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog)); + if (NS_SUCCEEDED(rv)) + { + nsString alertString; + // Format the alert string if parameter list isn't empty + if (params) + mLocalBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(err_code).get(), + params, length, getter_Copies(alertString)); + else + mLocalBundle->GetStringFromName(NS_ConvertASCIItoUTF16(err_code).get(), + getter_Copies(alertString)); + if (m_pop3ConData->command_succeeded) //not a server error message + dialog->Alert(dialogTitle.get(), alertString.get()); + else + { + nsString serverSaidPrefix; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsCString hostName; + // Fomat string with hostname. + if (server) + rv = server->GetRealHostName(hostName); + if (NS_SUCCEEDED(rv)) + { + nsAutoString hostStr; + CopyASCIItoUTF16(hostName, hostStr); + const char16_t *params[] = { hostStr.get() }; + mLocalBundle->FormatStringFromName( + u"pop3ServerSaid", + params, 1, getter_Copies(serverSaidPrefix)); + } + + nsAutoString message(alertString); + message.AppendLiteral(" "); + message.Append(serverSaidPrefix); + message.AppendLiteral(" "); + message.Append(NS_ConvertASCIItoUTF16(m_commandResponse)); + dialog->Alert(dialogTitle.get(), message.get()); + } + } + } + } + m_pop3ConData->next_state = POP3_ERROR_DONE; + m_pop3ConData->pause_for_read = false; + return -1; +} + +int32_t nsPop3Protocol::Pop3SendData(const char * dataBuffer, bool aSuppressLogging) +{ + // remove any leftover bytes in the line buffer + // this can happen if the last message line doesn't end with a (CR)LF + // or a server sent two reply lines + m_lineStreamBuffer->ClearBuffer(); + + nsresult result = nsMsgProtocol::SendData(dataBuffer); + + if (!aSuppressLogging) + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("SEND: %s"), dataBuffer)); + else + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, + (POP3LOG("Logging suppressed for this command (it probably contained authentication information)"))); + + if (NS_SUCCEEDED(result)) + { + m_pop3ConData->pause_for_read = true; + m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE; + return 0; + } + + m_pop3ConData->next_state = POP3_ERROR_DONE; + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Pop3SendData failed: %lx"), result)); + return -1; +} + +/* + * POP3 AUTH extension + */ + +int32_t nsPop3Protocol::SendAuth() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendAuth()"))); + + if(!m_pop3ConData->command_succeeded) + return Error("pop3ServerError"); + + nsAutoCString command("AUTH" CRLF); + + m_pop3ConData->next_state_after_response = POP3_AUTH_RESPONSE; + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::AuthResponse(nsIInputStream* inputStream, + uint32_t length) +{ + char * line; + uint32_t ln = 0; + nsresult rv; + + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + { + ClearCapFlag(POP3_AUTH_MECH_UNDEFINED); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + if (!m_pop3ConData->command_succeeded) + { + /* AUTH command not implemented + * so no secure mechanisms available + */ + m_pop3ConData->command_succeeded = true; + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + m_pop3ConData->next_state = POP3_SEND_CAPA; + return 0; + } + + bool pauseForMoreData = false; + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + PR_Free(line); + return(0); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + if (!PL_strcmp(line, ".")) + { + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + // now that we've read all the AUTH responses, go for it + m_pop3ConData->next_state = POP3_SEND_CAPA; + m_pop3ConData->pause_for_read = false; /* don't pause */ + } + else if (!PL_strcasecmp (line, "CRAM-MD5")) + SetCapFlag(POP3_HAS_AUTH_CRAM_MD5); + else if (!PL_strcasecmp (line, "NTLM")) + SetCapFlag(POP3_HAS_AUTH_NTLM); + else if (!PL_strcasecmp (line, "MSN")) + SetCapFlag(POP3_HAS_AUTH_NTLM|POP3_HAS_AUTH_MSN); + else if (!PL_strcasecmp (line, "GSSAPI")) + SetCapFlag(POP3_HAS_AUTH_GSSAPI); + else if (!PL_strcasecmp (line, "PLAIN")) + SetCapFlag(POP3_HAS_AUTH_PLAIN); + else if (!PL_strcasecmp (line, "LOGIN")) + SetCapFlag(POP3_HAS_AUTH_LOGIN); + + PR_Free(line); + return 0; +} + +/* + * POP3 CAPA extension, see RFC 2449, chapter 5 + */ + +int32_t nsPop3Protocol::SendCapa() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendCapa()"))); + if(!m_pop3ConData->command_succeeded) + return Error("pop3ServerError"); + + nsAutoCString command("CAPA" CRLF); + + m_pop3ConData->next_state_after_response = POP3_CAPA_RESPONSE; + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::CapaResponse(nsIInputStream* inputStream, + uint32_t length) +{ + char * line; + uint32_t ln = 0; + + if (!m_pop3ConData->command_succeeded) + { + /* CAPA command not implemented */ + m_pop3ConData->command_succeeded = true; + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + return 0; + } + + bool pauseForMoreData = false; + nsresult rv; + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + PR_Free(line); + return(0); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + if (!PL_strcmp(line, ".")) + { + // now that we've read all the CAPA responses, go for it + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; /* don't pause */ + } + else + if (!PL_strcasecmp(line, "XSENDER")) + { + SetCapFlag(POP3_HAS_XSENDER); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 2449, chapter 6.4 + if (!PL_strcasecmp(line, "RESP-CODES")) + { + SetCapFlag(POP3_HAS_RESP_CODES); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 3206, chapter 6 + if (!PL_strcasecmp(line, "AUTH-RESP-CODE")) + { + SetCapFlag(POP3_HAS_AUTH_RESP_CODE); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 2595, chapter 4 + if (!PL_strcasecmp(line, "STLS")) + { + SetCapFlag(POP3_HAS_STLS); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 2449, chapter 6.3 + if (!PL_strncasecmp(line, "SASL", 4) && strlen(line) > 6) + { + nsAutoCString responseLine; + responseLine.Assign(line + 5); + + if (responseLine.Find("PLAIN", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_PLAIN); + + if (responseLine.Find("LOGIN", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_LOGIN); + + if (responseLine.Find("GSSAPI", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_GSSAPI); + + if (responseLine.Find("CRAM-MD5", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_CRAM_MD5); + + if (responseLine.Find("NTLM", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_NTLM); + + if (responseLine.Find("MSN", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_NTLM|POP3_HAS_AUTH_MSN); + + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + PR_Free(line); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Capability entry processed"))); + return 0; +} + +int32_t nsPop3Protocol::SendTLSResponse() +{ + // only tear down our existing connection and open a new one if we received + // a +OK response from the pop server after we issued the STLS command + nsresult rv = NS_OK; + if (m_pop3ConData->command_succeeded) + { + nsCOMPtr<nsISupports> secInfo; + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv); + if (NS_FAILED(rv)) + return -1; + + rv = strans->GetSecurityInfo(getter_AddRefs(secInfo)); + + if (NS_SUCCEEDED(rv) && secInfo) + { + nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv); + + if (NS_SUCCEEDED(rv) && sslControl) + rv = sslControl->StartTLS(); + } + + if (NS_SUCCEEDED(rv)) + { + m_pop3ConData->next_state = POP3_SEND_AUTH; + m_tlsEnabled = true; + + // certain capabilities like POP3_HAS_AUTH_APOP should be + // preserved across the connections. + uint32_t preservedCapFlags = m_pop3ConData->capability_flags & POP3_HAS_AUTH_APOP; + m_pop3ConData->capability_flags = // resetting the flags + POP3_AUTH_MECH_UNDEFINED | + POP3_HAS_AUTH_USER | // should be always there + POP3_GURL_UNDEFINED | + POP3_UIDL_UNDEFINED | + POP3_TOP_UNDEFINED | + POP3_XTND_XLST_UNDEFINED | + preservedCapFlags; + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + return 0; + } + } + + ClearFlag(POP3_HAS_STLS); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + + return (NS_SUCCEEDED(rv) ? 0 : -1); +} + +void nsPop3Protocol::InitPrefAuthMethods(int32_t authMethodPrefValue) +{ + // for m_prefAuthMethods, using the same flags as server capablities. + switch (authMethodPrefValue) + { + case nsMsgAuthMethod::none: + m_prefAuthMethods = POP3_HAS_AUTH_NONE; + break; + case nsMsgAuthMethod::old: + m_prefAuthMethods = POP3_HAS_AUTH_USER; + break; + case nsMsgAuthMethod::passwordCleartext: + m_prefAuthMethods = POP3_HAS_AUTH_USER | + POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN; + break; + case nsMsgAuthMethod::passwordEncrypted: + m_prefAuthMethods = POP3_HAS_AUTH_CRAM_MD5 | + POP3_HAS_AUTH_APOP; + break; + case nsMsgAuthMethod::NTLM: + m_prefAuthMethods = POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN; + break; + case nsMsgAuthMethod::GSSAPI: + m_prefAuthMethods = POP3_HAS_AUTH_GSSAPI; + break; + case nsMsgAuthMethod::secure: + m_prefAuthMethods = POP3_HAS_AUTH_APOP | + POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_GSSAPI | + POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN; + break; + default: + NS_ASSERTION(false, "POP: authMethod pref invalid"); + // TODO log to error console + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("POP: bad pref authMethod = %d\n"), authMethodPrefValue)); + // fall to any + MOZ_FALLTHROUGH; + case nsMsgAuthMethod::anything: + m_prefAuthMethods = POP3_HAS_AUTH_USER | + POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN | + POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP | + POP3_HAS_AUTH_GSSAPI | + POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN; + // TODO needed? + break; + } + NS_ASSERTION(m_prefAuthMethods != POP3_AUTH_MECH_UNDEFINED, + "POP: InitPrefAuthMethods() didn't work"); +} + +/** + * Changes m_currentAuthMethod to pick the best one + * which is allowed by server and prefs and not marked failed. + * The order of preference and trying of auth methods is encoded here. + */ +nsresult nsPop3Protocol::ChooseAuthMethod() +{ + int32_t availCaps = GetCapFlags() & m_prefAuthMethods & ~m_failedAuthMethods; + + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("POP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X"), + GetCapFlags(), m_prefAuthMethods, m_failedAuthMethods, availCaps)); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("(GSSAPI = 0x%X, CRAM = 0x%X, APOP = 0x%X, NTLM = 0x%X, " + "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X)"), + POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP, + POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN, + POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER)); + + if (POP3_HAS_AUTH_GSSAPI & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_GSSAPI; + else if (POP3_HAS_AUTH_CRAM_MD5 & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_CRAM_MD5; + else if (POP3_HAS_AUTH_APOP & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_APOP; + else if (POP3_HAS_AUTH_NTLM & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_NTLM; + else if (POP3_HAS_AUTH_MSN & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_MSN; + else if (POP3_HAS_AUTH_PLAIN & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_PLAIN; + else if (POP3_HAS_AUTH_LOGIN & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_LOGIN; + else if (POP3_HAS_AUTH_USER & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_USER; + else + { + // there are no matching login schemes at all, per server and prefs + m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED; + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("no auth method remaining"))); + return NS_ERROR_FAILURE; + } + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("trying auth method 0x%X"), m_currentAuthMethod)); + return NS_OK; +} + +void nsPop3Protocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("marking auth method 0x%X failed"), failedAuthMethod)); + m_failedAuthMethods |= failedAuthMethod; +} + +/** + * Start over, trying all auth methods again + */ +void nsPop3Protocol::ResetAuthMethods() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("resetting (failed) auth methods"))); + m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED; + m_failedAuthMethods = 0; +} + +/** + * state POP3_PROCESS_AUTH + * Called when we should try to authenticate to the server. + * Also called when one auth method fails and we want to try and start + * the next best auth method. + */ +int32_t nsPop3Protocol::ProcessAuth() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("ProcessAuth()"))); + + // Try to upgrade to STARTTLS -- TODO move into its own function + if (!m_tlsEnabled) + { + if(TestCapFlag(POP3_HAS_STLS)) + { + if (m_socketType == nsMsgSocketType::trySTARTTLS || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + { + nsAutoCString command("STLS" CRLF); + + m_pop3ConData->next_state_after_response = POP3_TLS_RESPONSE; + return Pop3SendData(command.get()); + } + } + else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + return Error("nsErrorCouldNotConnectViaTls"); + } + } + + m_password_already_sent = false; + + nsresult rv = ChooseAuthMethod(); + if (NS_FAILED(rv)) + { + // Pref doesn't match server. Now, find an appropriate error msg. + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("ProcessAuth() early exit because no auth methods"))); + + // AuthGSSAPI* falls in here in case of an auth failure. + // If Kerberos was the only method, assume that + // the user is just not logged in yet, and show an appropriate error. + if (m_prefAuthMethods == POP3_HAS_AUTH_GSSAPI && + m_failedAuthMethods == POP3_HAS_AUTH_GSSAPI) + return Error("pop3GssapiFailure"); + + // pref has plaintext pw & server claims to support encrypted pw + if (m_prefAuthMethods == (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | + POP3_HAS_AUTH_PLAIN) && + GetCapFlags() & (POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP)) + // tell user to change to encrypted pw + return Error("pop3AuthChangePlainToEncrypt"); + // pref has encrypted pw & server claims to support plaintext pw + else if (m_prefAuthMethods == (POP3_HAS_AUTH_CRAM_MD5 | + POP3_HAS_AUTH_APOP) && + GetCapFlags() & (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | + POP3_HAS_AUTH_PLAIN)) + { + // have SSL + if (m_socketType == nsMsgSocketType::SSL || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + // tell user to change to plaintext pw + return Error("pop3AuthChangeEncryptToPlainSSL"); + else + // tell user to change to plaintext pw, with big warning + return Error("pop3AuthChangeEncryptToPlainNoSSL"); + } + else + // just "change auth method" + return Error("pop3AuthMechNotSupported"); + } + + switch (m_currentAuthMethod) + { + case POP3_HAS_AUTH_GSSAPI: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP GSSAPI"))); + m_pop3ConData->next_state = POP3_AUTH_GSSAPI; + break; + case POP3_HAS_AUTH_APOP: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP APOP"))); + m_pop3ConData->next_state = POP3_SEND_PASSWORD; + break; + case POP3_HAS_AUTH_CRAM_MD5: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP CRAM"))); + MOZ_FALLTHROUGH; + case POP3_HAS_AUTH_PLAIN: + case POP3_HAS_AUTH_USER: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP username"))); + m_pop3ConData->next_state = POP3_SEND_USERNAME; + break; + case POP3_HAS_AUTH_LOGIN: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP AUTH=LOGIN"))); + m_pop3ConData->next_state = POP3_AUTH_LOGIN; + break; + case POP3_HAS_AUTH_NTLM: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP NTLM"))); + m_pop3ConData->next_state = POP3_AUTH_NTLM; + break; + case POP3_HAS_AUTH_NONE: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP no auth"))); + m_pop3ConData->command_succeeded = true; + m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP; + break; + default: + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("POP: m_currentAuthMethod has unknown value"))); + return Error("pop3AuthMechNotSupported"); + } + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +/** + * state POP3_NEXT_AUTH_STEP + * This is called when we finished one auth step (e.g. sending username + * or password are separate steps, similarly for AUTH LOGIN, NTLM etc.) + * and want to proceed to the next one. + */ +int32_t nsPop3Protocol::NextAuthStep() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("NextAuthStep()"))); + if (m_pop3ConData->command_succeeded) + { + if (m_password_already_sent || // (also true for GSSAPI) + m_currentAuthMethod == POP3_HAS_AUTH_NONE) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("login succeeded"))); + m_nsIPop3Sink->SetUserAuthenticated(true); + ClearFlag(POP3_PASSWORD_FAILED); + if (m_pop3ConData->verify_logon) + m_pop3ConData->next_state = POP3_SEND_QUIT; + else + m_pop3ConData->next_state = (m_pop3ConData->get_url) + ? POP3_SEND_GURL : POP3_SEND_STAT; + } + else + m_pop3ConData->next_state = POP3_SEND_PASSWORD; + } + else + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("command did not succeed"))); + // response code received shows that login failed not because of + // wrong credential -> stop login without retry or pw dialog, only alert + // parameter list -> user + nsCString userName; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsresult rv = server->GetRealUsername(userName); + NS_ENSURE_SUCCESS(rv, -1); + NS_ConvertUTF8toUTF16 userNameUTF16(userName); + const char16_t* params[] = { userNameUTF16.get() }; + if (TestFlag(POP3_STOPLOGIN)) + { + if (m_password_already_sent) + return Error("pop3PasswordFailed", params, 1); + + return Error("pop3UsernameFailure"); + } + // response code received shows that server is certain about the + // credential was wrong -> no fallback, show alert and pw dialog + if (TestFlag(POP3_AUTH_FAILURE)) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("auth failure, setting password failed"))); + if (m_password_already_sent) + Error("pop3PasswordFailed", params, 1); + else + Error("pop3UsernameFailure"); + SetFlag(POP3_PASSWORD_FAILED); + ClearFlag(POP3_AUTH_FAILURE); + return 0; + } + + // We have no certain response code -> fallback and try again. + // Mark the auth method failed, to use a different method next round. + MarkAuthMethodAsFailed(m_currentAuthMethod); + + if (m_currentAuthMethod == POP3_HAS_AUTH_USER && + !m_password_already_sent) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER username failed"))); + // if USER auth method failed before sending the password, + // the username was wrong. + // no fallback but return error + return Error("pop3UsernameFailure"); + } + + // If we have no auth method left, ask user to try with new password + rv = ChooseAuthMethod(); + if (NS_FAILED(rv)) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("POP: no auth methods remaining, setting password failure"))); + /* Sever the connection and go back to the `read password' state, + which, upon success, will re-open the connection. Set a flag + which causes the prompt to be different that time (to indicate + that the old password was bogus.) + + But if we're just checking for new mail (biff) then don't bother + prompting the user for a password: just fail silently. + */ + SetFlag(POP3_PASSWORD_FAILED); + Error("pop3PasswordFailed", params, 1); + return 0; + } + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("still have some auth methods to try"))); + + // TODO needed? + //m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + m_pop3ConData->command_succeeded = true; + + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + } + + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + { + ClearCapFlag(POP3_AUTH_MECH_UNDEFINED); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +// LOGIN consists of three steps not two as USER/PASS or CRAM-MD5, +// so we've to start here and continue in SendUsername if the server +// responds + to "AUTH LOGIN" +int32_t nsPop3Protocol::AuthLogin() +{ + nsAutoCString command("AUTH LOGIN" CRLF); + m_pop3ConData->next_state_after_response = POP3_AUTH_LOGIN_RESPONSE; + m_pop3ConData->pause_for_read = true; + + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::AuthLoginResponse() +{ + // need the test to be here instead in NextAuthStep() to + // differentiate between command AUTH LOGIN failed and + // sending username using LOGIN mechanism failed. + if (!m_pop3ConData->command_succeeded) + { + // we failed with LOGIN, remove it + MarkAuthMethodAsFailed(POP3_HAS_AUTH_LOGIN); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + } + else + m_pop3ConData->next_state = POP3_SEND_USERNAME; + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +// NTLM, like LOGIN consists of three steps not two as USER/PASS or CRAM-MD5, +// so we've to start here and continue in SendUsername if the server +// responds + to "AUTH NTLM" +int32_t nsPop3Protocol::AuthNtlm() +{ + nsAutoCString command (m_currentAuthMethod == POP3_HAS_AUTH_MSN + ? "AUTH MSN" CRLF : "AUTH NTLM" CRLF); + m_pop3ConData->next_state_after_response = POP3_AUTH_NTLM_RESPONSE; + m_pop3ConData->pause_for_read = true; + + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::AuthNtlmResponse() +{ + // need the test to be here instead in NextAuthStep() to + // differentiate between command AUTH NTLM failed and + // sending username using NTLM mechanism failed. + if (!m_pop3ConData->command_succeeded) + { + MarkAuthMethodAsFailed(POP3_HAS_AUTH_NTLM); + MarkAuthMethodAsFailed(POP3_HAS_AUTH_MSN); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + } + else + m_pop3ConData->next_state = POP3_SEND_USERNAME; + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +int32_t nsPop3Protocol::AuthGSSAPI() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("AuthGSSAPI()"))); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) { + nsAutoCString cmd; + nsAutoCString service("pop@"); + nsCString hostName; + nsresult rv; + server->GetRealHostName(hostName); + service.Append(hostName); + rv = DoGSSAPIStep1(service.get(), m_username.get(), cmd); + if (NS_SUCCEEDED(rv)) { + m_GSSAPICache.Assign(cmd); + m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_FIRST; + m_pop3ConData->pause_for_read = true; + return Pop3SendData("AUTH GSSAPI" CRLF); + } + } + + MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; + return 0; +} + +int32_t nsPop3Protocol::AuthGSSAPIResponse(bool first) +{ + if (!m_pop3ConData->command_succeeded) + { + if (first) + m_GSSAPICache.Truncate(); + MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; + return 0; + } + + int32_t result; + + m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_STEP; + m_pop3ConData->pause_for_read = true; + + if (first) { + m_GSSAPICache += CRLF; + result = Pop3SendData(m_GSSAPICache.get()); + m_GSSAPICache.Truncate(); + } + else { + nsAutoCString cmd; + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("GSSAPI step 2"))); + nsresult rv = DoGSSAPIStep2(m_commandResponse, cmd); + if (NS_FAILED(rv)) + cmd = "*"; + if (rv == NS_SUCCESS_AUTH_FINISHED) { + m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP; + m_password_already_sent = true; + } + cmd += CRLF; + result = Pop3SendData(cmd.get()); + } + + return result; +} + +int32_t nsPop3Protocol::SendUsername() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendUsername()"))); + if(m_username.IsEmpty()) + return Error("pop3UsernameUndefined"); + + // <copied from="SendPassword()"> + // Needed for NTLM + + // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already + // got the password - they will have cancelled if necessary. + // If the password is still empty here, don't try to go on. + if (m_passwordResult.IsEmpty()) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + return Error("pop3PasswordUndefined"); + } + // </copied> + + nsAutoCString cmd; + + if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM) + (void) DoNtlmStep1(m_username.get(), m_passwordResult.get(), cmd); + else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5) + cmd = "AUTH CRAM-MD5"; + else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN) + cmd = "AUTH PLAIN"; + else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN) + { + char *base64Str = PL_Base64Encode(m_username.get(), m_username.Length(), nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_USER) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER login"))); + cmd = "USER "; + cmd += m_username; + } + else + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("In nsPop3Protocol::SendUsername(), m_currentAuthMethod is 0x%X, " + "but that is unexpected"), m_currentAuthMethod)); + return Error("pop3AuthInternalError"); + } + + cmd += CRLF; + + m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP; + + m_pop3ConData->pause_for_read = true; + + return Pop3SendData(cmd.get()); +} + +int32_t nsPop3Protocol::SendPassword() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendPassword()"))); + if (m_username.IsEmpty()) + return Error("pop3UsernameUndefined"); + + // <copied to="SendUsername()"> + // Needed here, too, because APOP skips SendUsername() + // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already + // got the password - they will have cancelled if necessary. + // If the password is still empty here, don't try to go on. + if (m_passwordResult.IsEmpty()) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + return Error("pop3PasswordUndefined"); + } + // </copied> + + nsAutoCString cmd; + nsresult rv; + + if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM) + rv = DoNtlmStep2(m_commandResponse, cmd); + else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("CRAM login"))); + char buffer[512]; // TODO nsAutoCString + unsigned char digest[DIGEST_LENGTH]; + + char *decodedChallenge = PL_Base64Decode(m_commandResponse.get(), + m_commandResponse.Length(), nullptr); + + if (decodedChallenge) + rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), + m_passwordResult.get(), m_passwordResult.Length(), digest); + else + rv = NS_ERROR_NULL_POINTER; + + if (NS_SUCCEEDED(rv)) + { + nsAutoCString encodedDigest; + char hexVal[8]; + + for (uint32_t j = 0; j < 16; j++) + { + PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]); + encodedDigest.Append(hexVal); + } + + PR_snprintf(buffer, sizeof(buffer), "%s %s", m_username.get(), + encodedDigest.get()); + char *base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + + if (NS_FAILED(rv)) + cmd = "*"; + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_APOP) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("APOP login"))); + char buffer[512]; + unsigned char digest[DIGEST_LENGTH]; + + rv = MSGApopMD5(m_ApopTimestamp.get(), m_ApopTimestamp.Length(), + m_passwordResult.get(), m_passwordResult.Length(), digest); + + if (NS_SUCCEEDED(rv)) + { + nsAutoCString encodedDigest; + char hexVal[8]; + + for (uint32_t j=0; j<16; j++) + { + PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]); + encodedDigest.Append(hexVal); + } + + PR_snprintf(buffer, sizeof(buffer), "APOP %s %s", m_username.get(), + encodedDigest.get()); + cmd = buffer; + } + + if (NS_FAILED(rv)) + cmd = "*"; + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PLAIN login"))); + // workaround for IPswitch's IMail server software + // this server goes into LOGIN mode even if we send "AUTH PLAIN" + // "VXNlc" is the beginning of the base64 encoded prompt ("Username:") for LOGIN + if (StringBeginsWith(m_commandResponse, NS_LITERAL_CSTRING("VXNlc"))) + { + // disable PLAIN and enable LOGIN (in case it's not already enabled) + ClearCapFlag(POP3_HAS_AUTH_PLAIN); + SetCapFlag(POP3_HAS_AUTH_LOGIN); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + // reenter authentication again at LOGIN response handler + m_pop3ConData->next_state = POP3_AUTH_LOGIN_RESPONSE; + m_pop3ConData->pause_for_read = false; + return 0; + } + + char plain_string[512]; // TODO nsCString + int len = 1; /* first <NUL> char */ + memset(plain_string, 0, 512); + PR_snprintf(&plain_string[1], 510, "%s", m_username.get()); + len += m_username.Length(); + len++; /* second <NUL> char */ + PR_snprintf(&plain_string[len], 511-len, "%s", m_passwordResult.get()); + len += m_passwordResult.Length(); + + char *base64Str = PL_Base64Encode(plain_string, len, nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LOGIN password"))); + char * base64Str = + PL_Base64Encode(m_passwordResult.get(), m_passwordResult.Length(), + nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_USER) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PASS password"))); + cmd = "PASS "; + cmd += m_passwordResult; + } + else + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("In nsPop3Protocol::SendPassword(), m_currentAuthMethod is %X, " + "but that is unexpected"), m_currentAuthMethod)); + return Error("pop3AuthInternalError"); + } + + cmd += CRLF; + + // TODO needed? + //m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP; + + m_pop3ConData->pause_for_read = true; + + m_password_already_sent = true; + m_lastPasswordSent = m_passwordResult; + return Pop3SendData(cmd.get(), true); +} + +int32_t nsPop3Protocol::SendStatOrGurl(bool sendStat) +{ + nsAutoCString cmd; + if (sendStat) + { + cmd = "STAT" CRLF; + m_pop3ConData->next_state_after_response = POP3_GET_STAT; + } + else + { + cmd = "GURL" CRLF; + m_pop3ConData->next_state_after_response = POP3_GURL_RESPONSE; + } + return Pop3SendData(cmd.get()); +} + + +int32_t +nsPop3Protocol::SendStat() +{ + return SendStatOrGurl(true); +} + + +int32_t +nsPop3Protocol::GetStat() +{ + // check stat response + if (!m_pop3ConData->command_succeeded) + return Error("pop3StatFail"); + + /* stat response looks like: %d %d + * The first number is the number of articles + * The second number is the number of bytes + * + * grab the first and second arg of stat response + */ + nsCString oldStr (m_commandResponse); + char *newStr = oldStr.BeginWriting(); + char *num = NS_strtok(" ", &newStr); // msg num + if (num) + { + m_pop3ConData->number_of_messages = atol(num); // bytes + num = NS_strtok(" ", &newStr); + m_commandResponse = newStr; + if (num) + m_totalFolderSize = nsCRT::atoll(num); //we always initialize m_totalFolderSize to 0 + } + else + m_pop3ConData->number_of_messages = 0; + + m_pop3ConData->really_new_messages = 0; + m_pop3ConData->real_new_counter = 1; + + m_totalDownloadSize = -1; // Means we need to calculate it, later. + + if (m_pop3ConData->number_of_messages <= 0) + { + // We're all done. We know we have no mail. + m_pop3ConData->next_state = POP3_SEND_QUIT; + PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash, hash_clear_mapper, nullptr); + // Hack - use nsPop3Sink to wipe out any stale Partial messages + m_nsIPop3Sink->BeginMailDelivery(false, nullptr, nullptr); + m_nsIPop3Sink->AbortMailDelivery(this); + return(0); + } + + /* We're just checking for new mail, and we're not playing any games that + involve keeping messages on the server. Therefore, we now know enough + to finish up. If we had no messages, that would have been handled + above; therefore, we know we have some new messages. + */ + if (m_pop3ConData->only_check_for_new_mail && !m_pop3ConData->leave_on_server) + { + m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, + m_pop3ConData->number_of_messages, + true); + m_pop3ConData->next_state = POP3_SEND_QUIT; + return(0); + } + + + if (!m_pop3ConData->only_check_for_new_mail) + { + /* The following was added to prevent the loss of Data when we try and + write to somewhere we don't have write access error to (See bug 62480) + (Note: This is only a temp hack until the underlying XPCOM is fixed + to return errors) */ + + nsresult rv; + nsCOMPtr <nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); +// NS_ASSERTION(NS_SUCCEEDED(rv) && msgWindow, "no msg window"); + + rv = m_nsIPop3Sink->BeginMailDelivery(m_pop3ConData->only_uidl != nullptr, msgWindow, + &m_pop3ConData->msg_del_started); + if (NS_FAILED(rv)) + { + m_nsIPop3Sink->AbortMailDelivery(this); + if (rv == NS_MSG_FOLDER_BUSY) { + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsString accountName; + rv = server->GetPrettyName(accountName); + NS_ENSURE_SUCCESS(rv, -1); + + const char16_t *params[] = { accountName.get() }; + return Error("pop3ServerBusy", params, 1); + } + + return Error("pop3MessageWriteError"); + } + + if(!m_pop3ConData->msg_del_started) + return Error("pop3MessageWriteError"); + } + + m_pop3ConData->next_state = POP3_SEND_LIST; + return 0; +} + + + +int32_t +nsPop3Protocol::SendGurl() +{ + if (m_pop3ConData->capability_flags == POP3_CAPABILITY_UNDEFINED || + TestCapFlag(POP3_GURL_UNDEFINED | POP3_HAS_GURL)) + return SendStatOrGurl(false); + else + return -1; +} + + +int32_t +nsPop3Protocol::GurlResponse() +{ + ClearCapFlag(POP3_GURL_UNDEFINED); + + if (m_pop3ConData->command_succeeded) + { + SetCapFlag(POP3_HAS_GURL); + if (m_nsIPop3Sink) + m_nsIPop3Sink->SetMailAccountURL(m_commandResponse); + } + else + { + ClearCapFlag(POP3_HAS_GURL); + } + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + m_pop3ConData->next_state = POP3_SEND_QUIT; + + return 0; +} + +int32_t nsPop3Protocol::SendList() +{ + // check for server returning number of messages that will cause the calculation + // of the size of the block for msg_info to + // overflow a 32 bit int, in turn causing us to allocate a block of memory much + // smaller than we think we're allocating, and + // potentially allowing the server to make us overwrite memory outside our heap + // block. + + if (m_pop3ConData->number_of_messages > (int) (0xFFFFF000 / sizeof(Pop3MsgInfo))) + return MK_OUT_OF_MEMORY; + + + m_pop3ConData->msg_info = (Pop3MsgInfo *) + PR_CALLOC(sizeof(Pop3MsgInfo) * m_pop3ConData->number_of_messages); + if (!m_pop3ConData->msg_info) + return(MK_OUT_OF_MEMORY); + m_pop3ConData->next_state_after_response = POP3_GET_LIST; + m_listpos = 0; + return Pop3SendData("LIST" CRLF); +} + + + +int32_t +nsPop3Protocol::GetList(nsIInputStream* inputStream, + uint32_t length) +{ + /* check list response + * This will get called multiple times + * but it's alright since command_succeeded + * will remain constant + */ + if(!m_pop3ConData->command_succeeded) + return Error("pop3ListFailure"); + + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if (pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; + PR_Free(line); + return(ln); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + /* parse the line returned from the list command + * it looks like + * #msg_number #bytes + * + * list data is terminated by a ".CRLF" line + */ + if (!PL_strcmp(line, ".")) + { + // limit the list if fewer entries than given in STAT response + if(m_listpos < m_pop3ConData->number_of_messages) + m_pop3ConData->number_of_messages = m_listpos; + m_pop3ConData->next_state = POP3_SEND_UIDL_LIST; + m_pop3ConData->pause_for_read = false; + PR_Free(line); + return(0); + } + + char *newStr = line; + char *token = NS_strtok(" ", &newStr); + if (token) + { + int32_t msg_num = atol(token); + + if (++m_listpos <= m_pop3ConData->number_of_messages) + { + token = NS_strtok(" ", &newStr); + if (token) + { + m_pop3ConData->msg_info[m_listpos-1].size = atol(token); + m_pop3ConData->msg_info[m_listpos-1].msgnum = msg_num; + } + } + } + + PR_Free(line); + return(0); +} + + +/* UIDL and XTND are both unsupported for this mail server. + If not enabled any advanced features, we're able to live + without them. We're simply downloading and deleting everything + on the server. + + Advanced features are: + *'Keep Mail on Server' with aging or deletion support + *'Fetch Headers Only' + *'Limit Message Size' + *only download a specific UID + + These require knowledge of of all messages UID's on the server at + least when it comes to deleting deleting messages on server that + have been deleted on client or vice versa. TOP doesn't help here + without generating huge traffic and is mostly not supported at all + if the server lacks UIDL and XTND XLST. + + In other cases the user has to join the 20th century. + Tell the user this, and refuse to download any messages until + they've gone into preferences and turned off any of the above + prefs. +*/ +int32_t nsPop3Protocol::HandleNoUidListAvailable() +{ + m_pop3ConData->pause_for_read = false; + + if(!m_pop3ConData->leave_on_server && + !m_pop3ConData->headers_only && + m_pop3ConData->size_limit <= 0 && + !m_pop3ConData->only_uidl) + { + m_pop3ConData->next_state = POP3_GET_MSG; + return 0; + } + m_pop3ConData->next_state = POP3_SEND_QUIT; + nsCString hostName; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsresult rv = server->GetRealHostName(hostName); + NS_ENSURE_SUCCESS(rv, -1); + NS_ConvertASCIItoUTF16 hostNameUnicode(hostName); + const char16_t *params[] = { hostNameUnicode.get() }; + return Error("pop3ServerDoesNotSupportUidlEtc", params, 1); +} + + +/* km + * + * net_pop3_send_xtnd_xlst_msgid + * + * Process state: POP3_SEND_XTND_XLST_MSGID + * + * If we get here then UIDL is not supported by the mail server. + * Some mail servers support a similar command: + * + * XTND XLST Message-Id + * + * Here is a sample transaction from a QUALCOMM server + + >>XTND XLST Message-Id + <<+OK xlst command accepted; headers coming. + <<1 Message-ID: <3117E4DC.2699@netscape.invalid> + <<2 Message-Id: <199602062335.PAA19215@lemon.example.com> + + * This function will send the xtnd command and put us into the + * POP3_GET_XTND_XLST_MSGID state + * +*/ +int32_t nsPop3Protocol::SendXtndXlstMsgid() +{ + if (TestCapFlag(POP3_HAS_XTND_XLST | POP3_XTND_XLST_UNDEFINED)) + { + m_pop3ConData->next_state_after_response = POP3_GET_XTND_XLST_MSGID; + m_pop3ConData->pause_for_read = true; + m_listpos = 0; + return Pop3SendData("XTND XLST Message-Id" CRLF); + } + else + return HandleNoUidListAvailable(); +} + + +/* km + * + * net_pop3_get_xtnd_xlst_msgid + * + * This code was created from the net_pop3_get_uidl_list boiler plate. + * The difference is that the XTND reply strings have one more token per + * string than the UIDL reply strings do. + * + */ + +int32_t +nsPop3Protocol::GetXtndXlstMsgid(nsIInputStream* inputStream, + uint32_t length) +{ + /* check list response + * This will get called multiple times + * but it's alright since command_succeeded + * will remain constant + */ + ClearCapFlag(POP3_XTND_XLST_UNDEFINED); + + if (!m_pop3ConData->command_succeeded) + { + ClearCapFlag(POP3_HAS_XTND_XLST); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + HandleNoUidListAvailable(); + return(0); + } + else + { + SetCapFlag(POP3_HAS_XTND_XLST); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if (pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; + PR_Free(line); + return ln; + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + /* parse the line returned from the list command + * it looks like + * 1 Message-ID: <3117E4DC.2699@example.com> + * + * list data is terminated by a ".CRLF" line + */ + if (!PL_strcmp(line, ".")) + { + // limit the list if fewer entries than given in STAT response + if(m_listpos < m_pop3ConData->number_of_messages) + m_pop3ConData->number_of_messages = m_listpos; + m_pop3ConData->list_done = true; + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->pause_for_read = false; + PR_Free(line); + return(0); + } + + char *newStr = line; + char *token = NS_strtok(" ", &newStr); // msg num + if (token) + { + int32_t msg_num = atol(token); + if (++m_listpos <= m_pop3ConData->number_of_messages) + { + NS_strtok(" ", &newStr); // eat message ID token + const char *uid = NS_strtok(" ", &newStr); // not really a UID but a unique token -km + if (!uid) + /* This is bad. The server didn't give us a UIDL for this message. + I've seen this happen when somehow the mail spool has a message + that contains a header that reads "X-UIDL: \n". But how that got + there, I have no idea; must be a server bug. Or something. */ + uid = ""; + + // seeking right entry, but try the one that should it be first + int32_t i; + if(m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num) + i = m_listpos - 1; + else + for(i = 0; i < m_pop3ConData->number_of_messages && + m_pop3ConData->msg_info[i].msgnum != msg_num; i++) + ; + + // only if found a matching slot + if (i < m_pop3ConData->number_of_messages) + { + // to protect us from memory leak in case of getting a msg num twice + m_pop3ConData->msg_info[i].uidl = PL_strdup(uid); + if (!m_pop3ConData->msg_info[i].uidl) + { + PR_Free(line); + return MK_OUT_OF_MEMORY; + } + } + } + } + + PR_Free(line); + return(0); +} + + +int32_t nsPop3Protocol::SendUidlList() +{ + if (TestCapFlag(POP3_HAS_UIDL | POP3_UIDL_UNDEFINED)) + { + m_pop3ConData->next_state_after_response = POP3_GET_UIDL_LIST; + m_pop3ConData->pause_for_read = true; + m_listpos = 0; + return Pop3SendData("UIDL" CRLF); + } + else + return SendXtndXlstMsgid(); +} + + +int32_t nsPop3Protocol::GetUidlList(nsIInputStream* inputStream, + uint32_t length) +{ + /* check list response + * This will get called multiple times + * but it's alright since command_succeeded + * will remain constant + */ + ClearCapFlag(POP3_UIDL_UNDEFINED); + + if (!m_pop3ConData->command_succeeded) + { + m_pop3ConData->next_state = POP3_SEND_XTND_XLST_MSGID; + m_pop3ConData->pause_for_read = false; + ClearCapFlag(POP3_HAS_UIDL); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + return(0); + } + else + { + SetCapFlag(POP3_HAS_UIDL); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if (pauseForMoreData || !line) + { + PR_Free(line); + m_pop3ConData->pause_for_read = true; + return ln; + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + /* parse the line returned from the list command + * it looks like + * #msg_number uidl + * + * list data is terminated by a ".CRLF" line + */ + if (!PL_strcmp(line, ".")) + { + // limit the list if fewer entries than given in STAT response + if (m_listpos < m_pop3ConData->number_of_messages) + m_pop3ConData->number_of_messages = m_listpos; + m_pop3ConData->list_done = true; + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->pause_for_read = false; + PR_Free(line); + return(0); + } + + char *newStr = line; + char *token = NS_strtok(" ", &newStr); // msg num + if (token) + { + int32_t msg_num = atol(token); + if (++m_listpos <= m_pop3ConData->number_of_messages) + { + const char *uid = NS_strtok(" ", &newStr); // UID + if (!uid) + /* This is bad. The server didn't give us a UIDL for this message. + I've seen this happen when somehow the mail spool has a message + that contains a header that reads "X-UIDL: \n". But how that got + there, I have no idea; must be a server bug. Or something. */ + uid = ""; + + // seeking right entry, but try the one that should it be first + int32_t i; + if(m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num) + i = m_listpos - 1; + else + for(i = 0; i < m_pop3ConData->number_of_messages && + m_pop3ConData->msg_info[i].msgnum != msg_num; i++) + ; + + // only if found a matching slot + if (i < m_pop3ConData->number_of_messages) + { + // to protect us from memory leak in case of getting a msg num twice + m_pop3ConData->msg_info[i].uidl = PL_strdup(uid); + if (!m_pop3ConData->msg_info[i].uidl) + { + PR_Free(line); + return MK_OUT_OF_MEMORY; + } + } + } + } + PR_Free(line); + return(0); +} + + + +/* this function decides if we are going to do a + * normal RETR or a TOP. The first time, it also decides the total number + * of bytes we're probably going to get. + */ +int32_t nsPop3Protocol::GetMsg() +{ + int32_t popstateTimestamp = TimeInSecondsFromPRTime(PR_Now()); + + if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages) + { + /* Oh, gee, we're all done. */ + if(m_pop3ConData->msg_del_started) + { + if (!m_pop3ConData->only_uidl) + { + if (m_pop3ConData->only_check_for_new_mail) + m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, m_pop3ConData->really_new_messages, true); + /* update old style biff */ + else + m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, m_pop3ConData->really_new_messages, false); + } + m_nsIPop3Sink->EndMailDelivery(this); + } + + m_pop3ConData->next_state = POP3_SEND_QUIT; + return 0; + } + + if (m_totalDownloadSize < 0) + { + /* First time. Figure out how many bytes we're about to get. + If we didn't get any message info, then we are going to get + everything, and it's easy. Otherwise, if we only want one + uidl, than that's the only one we'll get. Otherwise, go + through each message info, decide if we're going to get that + message, and add the number of bytes for it. When a message is too + large (per user's preferences) only add the size we are supposed + to get. */ + m_pop3ConData->really_new_messages = 0; + m_pop3ConData->real_new_counter = 1; + if (m_pop3ConData->msg_info) + { + m_totalDownloadSize = 0; + for (int32_t i = 0; i < m_pop3ConData->number_of_messages; i++) + { + if (m_pop3ConData->only_uidl) + { + if (m_pop3ConData->msg_info[i].uidl && + !PL_strcmp(m_pop3ConData->msg_info[i].uidl, m_pop3ConData->only_uidl)) + { + m_totalDownloadSize = m_pop3ConData->msg_info[i].size; + m_pop3ConData->really_new_messages = 1; + // we are only getting one message + m_pop3ConData->real_new_counter = 1; + break; + } + continue; + } + + char c = 0; + popstateTimestamp = TimeInSecondsFromPRTime(PR_Now()); + if (m_pop3ConData->msg_info[i].uidl) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, + m_pop3ConData->msg_info[i].uidl); + if (uidlEntry) + { + c = uidlEntry->status; + popstateTimestamp = uidlEntry->dateReceived; + } + } + if ((c == KEEP) && !m_pop3ConData->leave_on_server) + { /* This message has been downloaded but kept on server, we + * no longer want to keep it there */ + if (!m_pop3ConData->newuidl) + { + m_pop3ConData->newuidl = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, + PL_CompareValues, &gHashAllocOps, nullptr); + if (!m_pop3ConData->newuidl) + return MK_OUT_OF_MEMORY; + } + c = DELETE_CHAR; + // Mark message to be deleted in new table + put_hash(m_pop3ConData->newuidl, + m_pop3ConData->msg_info[i].uidl, DELETE_CHAR, popstateTimestamp); + // and old one too + put_hash(m_pop3ConData->uidlinfo->hash, + m_pop3ConData->msg_info[i].uidl, DELETE_CHAR, popstateTimestamp); + } + if ((c != KEEP) && (c != DELETE_CHAR) && (c != TOO_BIG)) + { // message left on server + m_totalDownloadSize += m_pop3ConData->msg_info[i].size; + m_pop3ConData->really_new_messages++; + // a message we will really download + } + } + } + else + { + m_totalDownloadSize = m_totalFolderSize; + } + if (m_pop3ConData->only_check_for_new_mail) + { + if (m_totalDownloadSize > 0) + { + m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NewMail; + m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, m_pop3ConData->really_new_messages, true); + } + m_pop3ConData->next_state = POP3_SEND_QUIT; + return(0); + } + /* get the amount of available space on the drive + * and make sure there is enough + */ + if (m_totalDownloadSize > 0) // skip all this if there aren't any messages + { + nsCOMPtr<nsIMsgFolder> folder; + + // Get the current mailbox folder + NS_ENSURE_TRUE(m_nsIPop3Sink, -1); + nsresult rv = m_nsIPop3Sink->GetFolder(getter_AddRefs(folder)); + if (NS_FAILED(rv)) + return -1; + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + if (NS_FAILED(rv) || !mailnewsUrl) + return -1; + + nsCOMPtr<nsIMsgWindow> msgWindow; + (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + + bool spaceNotAvailable = true; + nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(folder, &rv)); + if (NS_FAILED(rv) || !localFolder) + return -1; + + // check if we have a reasonable amount of space left + rv = localFolder->WarnIfLocalFileTooBig(msgWindow, m_totalDownloadSize, &spaceNotAvailable); + if (NS_FAILED(rv) || spaceNotAvailable) { +#ifdef DEBUG + printf("Not enough disk space! Raising error!\n"); +#endif + return -1; + } + + // Here we know how many messages we're going to download, so let + // the pop3 sink know. + rv = m_nsIPop3Sink->SetMsgsToDownload(m_pop3ConData->really_new_messages); + } + } + + /* Look at this message, and decide whether to ignore it, get it, just get + the TOP of it, or delete it. */ + + // if this is a message we've seen for the first time, we won't find it in + // m_pop3ConData-uidlinfo->hash. By default, we retrieve messages, unless they have a status, + // or are too big, in which case we figure out what to do. + if (m_prefAuthMethods != POP3_HAS_AUTH_USER && TestCapFlag(POP3_HAS_XSENDER)) + m_pop3ConData->next_state = POP3_SEND_XSENDER; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + m_pop3ConData->truncating_cur_msg = false; + m_pop3ConData->pause_for_read = false; + if (m_pop3ConData->msg_info) + { + Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg; + if (m_pop3ConData->only_uidl) + { + if (info->uidl == NULL || PL_strcmp(info->uidl, m_pop3ConData->only_uidl)) + m_pop3ConData->next_state = POP3_GET_MSG; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + } + else + { + char c = 0; + if (!m_pop3ConData->newuidl) + { + m_pop3ConData->newuidl = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr); + if (!m_pop3ConData->newuidl) + return MK_OUT_OF_MEMORY; + } + if (info->uidl) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, info->uidl); + if (uidlEntry) + { + c = uidlEntry->status; + popstateTimestamp = uidlEntry->dateReceived; + } + } + if (c == DELETE_CHAR) + { + m_pop3ConData->next_state = POP3_SEND_DELE; + } + else if (c == KEEP) + { + // this is a message we've already downloaded and left on server; + // Advance to next message. + m_pop3ConData->next_state = POP3_GET_MSG; + } + else if (c == FETCH_BODY) + { + m_pop3ConData->next_state = POP3_SEND_RETR; + PL_HashTableRemove (m_pop3ConData->uidlinfo->hash, (void*)info->uidl); + } + else if ((c != TOO_BIG) && + (TestCapFlag(POP3_TOP_UNDEFINED | POP3_HAS_TOP)) && + (m_pop3ConData->headers_only || + ((m_pop3ConData->size_limit > 0) && + (info->size > m_pop3ConData->size_limit) && + !m_pop3ConData->only_uidl)) && + info->uidl && *info->uidl) + { + // message is too big + m_pop3ConData->truncating_cur_msg = true; + m_pop3ConData->next_state = POP3_SEND_TOP; + put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp); + } + else if (c == TOO_BIG) + { + /* message previously left on server, see if the max download size + has changed, because we may want to download the message this time + around. Otherwise ignore the message, we have the header. */ + if ((m_pop3ConData->size_limit > 0) && (info->size <= + m_pop3ConData->size_limit)) + PL_HashTableRemove (m_pop3ConData->uidlinfo->hash, (void*)info->uidl); + // remove from our table, and download + else + { + m_pop3ConData->truncating_cur_msg = true; + m_pop3ConData->next_state = POP3_GET_MSG; + // ignore this message and get next one + put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp); + } + } + + if (m_pop3ConData->next_state != POP3_SEND_DELE && + info->uidl) + { + /* This is a message we have decided to keep on the server. Notate + that now for the future. (Don't change the popstate file at all + if only_uidl is set; in that case, there might be brand new messages + on the server that we *don't* want to mark KEEP; we just want to + leave them around until the user next does a GetNewMail.) */ + + /* If this is a message we already know about (i.e., it was + in popstate.dat already), we need to maintain the original + date the message was downloaded. */ + if (m_pop3ConData->truncating_cur_msg) + put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp); + else + put_hash(m_pop3ConData->newuidl, info->uidl, KEEP, popstateTimestamp); + } + } + if (m_pop3ConData->next_state == POP3_GET_MSG) + m_pop3ConData->last_accessed_msg++; + // Make sure we check the next message next time! + } + return 0; +} + + +/* start retreiving just the first 20 lines + */ +int32_t nsPop3Protocol::SendTop() +{ + char * cmd = PR_smprintf( "TOP %ld %d" CRLF, + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum, + m_pop3ConData->headers_only ? 0 : 20); + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_TOP_RESPONSE; + m_pop3ConData->cur_msg_size = -1; + + /* zero the bytes received in message in preparation for + * the next + */ + m_bytesInMsgReceived = 0; + status = Pop3SendData(cmd); + } + PR_Free(cmd); + return status; +} + +/* send the xsender command + */ +int32_t nsPop3Protocol::SendXsender() +{ + char * cmd = PR_smprintf("XSENDER %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum); + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_XSENDER_RESPONSE; + status = Pop3SendData(cmd); + PR_Free(cmd); + } + return status; +} + +int32_t nsPop3Protocol::XsenderResponse() +{ + m_pop3ConData->seenFromHeader = false; + m_senderInfo = ""; + + if (m_pop3ConData->command_succeeded) { + if (m_commandResponse.Length() > 4) + m_senderInfo = m_commandResponse; + } + else { + ClearCapFlag(POP3_HAS_XSENDER); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + if (m_pop3ConData->truncating_cur_msg) + m_pop3ConData->next_state = POP3_SEND_TOP; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + return 0; +} + +/* retreive the whole message + */ +int32_t +nsPop3Protocol::SendRetr() +{ + + char * cmd = PR_smprintf("RETR %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum); + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_RETR_RESPONSE; + m_pop3ConData->cur_msg_size = -1; + + + /* zero the bytes received in message in preparation for + * the next + */ + m_bytesInMsgReceived = 0; + + if (m_pop3ConData->only_uidl) + { + /* Display bytes if we're only downloading one message. */ + PR_ASSERT(!m_pop3ConData->graph_progress_bytes_p); + UpdateProgressPercent(0, m_totalDownloadSize); + m_pop3ConData->graph_progress_bytes_p = true; + } + else + { + nsString finalString; + mozilla::DebugOnly<nsresult> rv = + FormatCounterString(NS_LITERAL_STRING("receivingMessages"), + m_pop3ConData->real_new_counter, + m_pop3ConData->really_new_messages, + finalString); + NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't format string"); + if (mProgressEventSink) { + rv = mProgressEventSink->OnStatus(this, m_channelContext, NS_OK, + finalString.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result"); + } + } + + status = Pop3SendData(cmd); + } // if cmd + PR_Free(cmd); + return status; +} + +/* digest the message + */ +int32_t +nsPop3Protocol::RetrResponse(nsIInputStream* inputStream, + uint32_t length) +{ + uint32_t buffer_size; + int32_t flags = 0; + char *uidl = NULL; + nsresult rv; + uint32_t status = 0; + + if(m_pop3ConData->cur_msg_size == -1) + { + /* this is the beginning of a message + * get the response code and byte size + */ + if(!m_pop3ConData->command_succeeded) + return Error("pop3RetrFailure"); + + /* a successful RETR response looks like: #num_bytes Junk + from TOP we only get the +OK and data + */ + if (m_pop3ConData->truncating_cur_msg) + { /* TOP, truncated message */ + flags |= nsMsgMessageFlags::Partial; + } + else + { + nsCString cmdResp(m_commandResponse); + char *newStr = cmdResp.BeginWriting(); + char *num = NS_strtok( " ", &newStr); + if (num) + m_pop3ConData->cur_msg_size = atol(num); + m_commandResponse = newStr; + } + + /* RETR complete message */ + if (!m_senderInfo.IsEmpty()) + flags |= nsMsgMessageFlags::SenderAuthed; + + if(m_pop3ConData->cur_msg_size <= 0) + { + if (m_pop3ConData->msg_info) + m_pop3ConData->cur_msg_size = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].size; + else + m_pop3ConData->cur_msg_size = 0; + } + + if (m_pop3ConData->msg_info && + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl) + uidl = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl; + + m_pop3ConData->parsed_bytes = 0; + m_pop3ConData->pop3_size = m_pop3ConData->cur_msg_size; + m_pop3ConData->assumed_end = false; + + m_pop3Server->GetDotFix(&m_pop3ConData->dot_fix); + + MOZ_LOG(POP3LOGMODULE,LogLevel::Info, + (POP3LOG("Opening message stream: MSG_IncorporateBegin"))); + + /* open the message stream so we have someplace + * to put the data + */ + m_pop3ConData->real_new_counter++; + /* (rb) count only real messages being downloaded */ + rv = m_nsIPop3Sink->IncorporateBegin(uidl, m_url, flags, + &m_pop3ConData->msg_closure); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Done opening message stream!"))); + + if(!m_pop3ConData->msg_closure || NS_FAILED(rv)) + return Error("pop3MessageWriteError"); + } + + m_pop3ConData->pause_for_read = true; + + bool pauseForMoreData = false; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv, true); + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + if (NS_FAILED(rv)) + return -1; + + buffer_size = status; + + if (status == 0 && !line) // no bytes read in... + return (0); + + if (m_pop3ConData->msg_closure) /* not done yet */ + { + // buffer the line we just read in, and buffer all remaining lines in the stream + status = buffer_size; + do + { + if (m_pop3ConData->msg_closure) + { + rv = HandleLine(line, buffer_size); + if (NS_FAILED(rv)) + return Error("pop3MessageWriteError"); + + // buffer_size already includes MSG_LINEBREAK_LEN so + // subtract and add CRLF + // but not really sure we always had CRLF in input since + // we also treat a single LF as line ending! + m_pop3ConData->parsed_bytes += buffer_size - MSG_LINEBREAK_LEN + 2; + } + + // now read in the next line + PR_Free(line); + line = m_lineStreamBuffer->ReadNextLine(inputStream, buffer_size, + pauseForMoreData, &rv, true); + if (NS_FAILED(rv)) + return -1; + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + // buffer_size already includes MSG_LINEBREAK_LEN so + // subtract and add CRLF + // but not really sure we always had CRLF in input since + // we also treat a single LF as line ending! + status += buffer_size - MSG_LINEBREAK_LEN + 2; + } while (line); + } + + buffer_size = status; // status holds # bytes we've actually buffered so far... + + /* normal read. Yay! */ + if ((int32_t) (m_bytesInMsgReceived + buffer_size) > m_pop3ConData->cur_msg_size) + buffer_size = m_pop3ConData->cur_msg_size - m_bytesInMsgReceived; + + m_bytesInMsgReceived += buffer_size; + m_totalBytesReceived += buffer_size; + + // *** jefft in case of the message size that server tells us is different + // from the actual message size + if (pauseForMoreData && m_pop3ConData->dot_fix && + m_pop3ConData->assumed_end && m_pop3ConData->msg_closure) + { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (NS_SUCCEEDED(rv)) + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + rv = m_nsIPop3Sink->IncorporateComplete(msgWindow, + m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0); + + // The following was added to prevent the loss of Data when we try + // and write to somewhere we don't have write access error to (See + // bug 62480) + // (Note: This is only a temp hack until the underlying XPCOM is + // fixed to return errors) + + if (NS_FAILED(rv)) + return Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD) ? + "pop3TmpDownloadError" : + "pop3MessageWriteError"); + + m_pop3ConData->msg_closure = nullptr; + } + + if (!m_pop3ConData->msg_closure) + /* meaning _handle_line read ".\r\n" at end-of-msg */ + { + m_pop3ConData->pause_for_read = false; + + if (m_pop3ConData->truncating_cur_msg || + m_pop3ConData->leave_on_server ) + { + Pop3UidlEntry *uidlEntry = NULL; + Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg; + + /* Check for filter actions - FETCH or DELETE */ + if ((m_pop3ConData->newuidl) && (info->uidl)) + uidlEntry = (Pop3UidlEntry *)PL_HashTableLookup(m_pop3ConData->newuidl, info->uidl); + + if (uidlEntry && uidlEntry->status == FETCH_BODY && + m_pop3ConData->truncating_cur_msg) + { + /* A filter decided to retrieve this full msg. + Use GetMsg() so the popstate will update correctly, + but don't let this msg get counted twice. */ + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->real_new_counter--; + /* Make sure we don't try to come through here again. */ + PL_HashTableRemove (m_pop3ConData->newuidl, (void*)info->uidl); + put_hash(m_pop3ConData->uidlinfo->hash, info->uidl, FETCH_BODY, uidlEntry->dateReceived); + + } else if (uidlEntry && uidlEntry->status == DELETE_CHAR) + { + // A filter decided to delete this msg from the server + m_pop3ConData->next_state = POP3_SEND_DELE; + } else + { + /* We've retrieved all or part of this message, but we want to + keep it on the server. Go on to the next message. */ + m_pop3ConData->last_accessed_msg++; + m_pop3ConData->next_state = POP3_GET_MSG; + } + if (m_pop3ConData->only_uidl) + { + /* GetMsg didn't update this field. Do it now */ + uidlEntry = (Pop3UidlEntry *)PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl); + NS_ASSERTION(uidlEntry, "uidl not found in uidlinfo"); + if (uidlEntry) + put_hash(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl, KEEP, uidlEntry->dateReceived); + } + } + else + { + m_pop3ConData->next_state = POP3_SEND_DELE; + } + + /* if we didn't get the whole message add the bytes that we didn't get + to the bytes received part so that the progress percent stays sane. + */ + if (m_bytesInMsgReceived < m_pop3ConData->cur_msg_size) + m_totalBytesReceived += (m_pop3ConData->cur_msg_size - + m_bytesInMsgReceived); + } + + /* set percent done to portion of total bytes of all messages + that we're going to download. */ + if (m_totalDownloadSize) + UpdateProgressPercent(m_totalBytesReceived, m_totalDownloadSize); + + PR_Free(line); + return(0); +} + + +int32_t +nsPop3Protocol::TopResponse(nsIInputStream* inputStream, uint32_t length) +{ + if (TestCapFlag(POP3_TOP_UNDEFINED)) + { + ClearCapFlag(POP3_TOP_UNDEFINED); + if (m_pop3ConData->command_succeeded) + SetCapFlag(POP3_HAS_TOP); + else + ClearCapFlag(POP3_HAS_TOP); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + if(m_pop3ConData->cur_msg_size == -1 && /* first line after TOP command sent */ + !m_pop3ConData->command_succeeded) /* and TOP command failed */ + { + /* TOP doesn't work so we can't retrieve the first part of this msg. + So just go download the whole thing, and warn the user. + + Note that the progress bar will not be accurate in this case. + Oops. #### */ + m_pop3ConData->truncating_cur_msg = false; + + nsString statusTemplate; + mLocalBundle->GetStringFromName( + u"pop3ServerDoesNotSupportTopCommand", + getter_Copies(statusTemplate)); + if (!statusTemplate.IsEmpty()) + { + nsAutoCString hostName; + char16_t * statusString = nullptr; + m_url->GetHost(hostName); + + statusString = nsTextFormatter::smprintf(statusTemplate.get(), hostName.get()); + UpdateStatusWithString(statusString); + nsTextFormatter::smprintf_free(statusString); + } + + if (m_prefAuthMethods != POP3_HAS_AUTH_USER && + TestCapFlag(POP3_HAS_XSENDER)) + m_pop3ConData->next_state = POP3_SEND_XSENDER; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + return(0); + } + + /* If TOP works, we handle it in the same way as RETR. */ + return RetrResponse(inputStream, length); +} + +/* line is handed over as null-terminated string with MSG_LINEBREAK */ +nsresult +nsPop3Protocol::HandleLine(char *line, uint32_t line_length) +{ + nsresult rv = NS_OK; + + NS_ASSERTION(m_pop3ConData->msg_closure, "m_pop3ConData->msg_closure is null in nsPop3Protocol::HandleLine()"); + if (!m_pop3ConData->msg_closure) + return NS_ERROR_NULL_POINTER; + + if (!m_senderInfo.IsEmpty() && !m_pop3ConData->seenFromHeader) + { + if (line_length > 6 && !PL_strncasecmp("From: ", line, 6)) + { + m_pop3ConData->seenFromHeader = true; + if (PL_strstr(line, m_senderInfo.get()) == NULL) + m_nsIPop3Sink->SetSenderAuthedFlag(m_pop3ConData->msg_closure, + false); + } + } + + // line contains only a single dot and linebreak -> message end + if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.') + { + m_pop3ConData->assumed_end = true; /* in case byte count from server is */ + /* wrong, mark we may have had the end */ + if (!m_pop3ConData->dot_fix || m_pop3ConData->truncating_cur_msg || + (m_pop3ConData->parsed_bytes >= (m_pop3ConData->pop3_size -3))) + { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (NS_SUCCEEDED(rv)) + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + rv = m_nsIPop3Sink->IncorporateComplete(msgWindow, + m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0); + + // The following was added to prevent the loss of Data when we try + // and write to somewhere we don't have write access error to (See + // bug 62480) + // (Note: This is only a temp hack until the underlying XPCOM is + // fixed to return errors) + + if (NS_FAILED(rv)) { + Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD) ? + "pop3TmpDownloadError" : + "pop3MessageWriteError"); + return rv; + } + + m_pop3ConData->msg_closure = nullptr; + return rv; + } + } + /* Check if the line begins with the termination octet. If so + and if another termination octet follows, we step over the + first occurence of it. */ + else if (line_length > 1 && line[0] == '.' && line[1] == '.') { + line++; + line_length--; + + } + + return m_nsIPop3Sink->IncorporateWrite(line, line_length); +} + +int32_t nsPop3Protocol::SendDele() +{ + /* increment the last accessed message since we have now read it + */ + char * cmd = PR_smprintf("DELE %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum); + m_pop3ConData->last_accessed_msg++; + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_DELE_RESPONSE; + status = Pop3SendData(cmd); + } + PR_Free(cmd); + return status; +} + +int32_t nsPop3Protocol::DeleResponse() +{ + Pop3UidlHost *host = NULL; + + host = m_pop3ConData->uidlinfo; + + /* the return from the delete will come here + */ + if(!m_pop3ConData->command_succeeded) + return Error("pop3DeleFailure"); + + + /* ###chrisf + the delete succeeded. Write out state so that we + keep track of all the deletes which have not yet been + committed on the server. Flush this state upon successful + QUIT. + + We will do this by adding each successfully deleted message id + to a list which we will write out to popstate.dat in + net_pop3_write_state(). + */ + if (host) + { + if (m_pop3ConData->msg_info && + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl) + { + if (m_pop3ConData->newuidl) + if (m_pop3ConData->leave_on_server) + { + PL_HashTableRemove(m_pop3ConData->newuidl, (void*) + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl); + } + else + { + put_hash(m_pop3ConData->newuidl, + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl, DELETE_CHAR, 0); + /* kill message in new hash table */ + } + else + PL_HashTableRemove(host->hash, + (void*) m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl); + } + } + + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->pause_for_read = false; + + return(0); +} + + +int32_t +nsPop3Protocol::CommitState(bool remove_last_entry) +{ + // only use newuidl if we successfully finished looping through all the + // messages in the inbox. + if (m_pop3ConData->newuidl) + { + if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages) + { + PL_HashTableDestroy(m_pop3ConData->uidlinfo->hash); + m_pop3ConData->uidlinfo->hash = m_pop3ConData->newuidl; + m_pop3ConData->newuidl = nullptr; + } + else + { + /* If we are leaving messages on the server, pull out the last + uidl from the hash, because it might have been put in there before + we got it into the database. + */ + if (remove_last_entry && m_pop3ConData->msg_info && + !m_pop3ConData->only_uidl && m_pop3ConData->newuidl->nentries > 0) + { + Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg; + if (info && info->uidl) + { + mozilla::DebugOnly<bool> val = PL_HashTableRemove(m_pop3ConData->newuidl, + info->uidl); + NS_ASSERTION(val, "uidl not in hash table"); + } + } + + // Add the entries in newuidl to m_pop3ConData->uidlinfo->hash to keep + // track of the messages we *did* download in this session. + PL_HashTableEnumerateEntries(m_pop3ConData->newuidl, net_pop3_copy_hash_entries, (void *)m_pop3ConData->uidlinfo->hash); + } + } + + if (!m_pop3ConData->only_check_for_new_mail) + { + nsresult rv; + nsCOMPtr<nsIFile> mailDirectory; + + // get the mail directory + nsCOMPtr<nsIMsgIncomingServer> server = + do_QueryInterface(m_pop3Server, &rv); + if (NS_FAILED(rv)) return -1; + + rv = server->GetLocalPath(getter_AddRefs(mailDirectory)); + if (NS_FAILED(rv)) return -1; + + // write the state in the mail directory + net_pop3_write_state(m_pop3ConData->uidlinfo, mailDirectory.get()); + } + return 0; +} + + +/* NET_process_Pop3 will control the state machine that + * loads messages from a pop3 server + * + * returns negative if the transfer is finished or error'd out + * + * returns zero or more if the transfer needs to be continued. + */ +nsresult nsPop3Protocol::ProcessProtocolState(nsIURI * url, nsIInputStream * aInputStream, + uint64_t sourceOffset, uint32_t aLength) +{ + int32_t status = 0; + bool urlStatusSet = false; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_url); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Entering NET_ProcessPop3 %d"), + aLength)); + + m_pop3ConData->pause_for_read = false; /* already paused; reset */ + + if(m_username.IsEmpty()) + { + // net_pop3_block = false; + Error("pop3UsernameUndefined"); + return NS_MSG_SERVER_USERNAME_MISSING; + } + + while(!m_pop3ConData->pause_for_read) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, + (POP3LOG("Entering state: %d"), m_pop3ConData->next_state)); + + switch(m_pop3ConData->next_state) + { + case POP3_READ_PASSWORD: + // This is a separate state so that we're waiting for the user to type + // in a password while we don't actually have a connection to the pop + // server open; this saves us from having to worry about the server + // timing out on us while we wait for user input. + if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_EARLY))) + status = -1; + break; + case POP3_FINISH_OBTAIN_PASSWORD_EARLY: + { + if (m_passwordResult.IsEmpty() || m_username.IsEmpty()) + { + status = MK_POP3_PASSWORD_UNDEFINED; + m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_Unknown; + m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, 0, false); + + /* update old style biff */ + m_pop3ConData->next_state = POP3_FREE; + m_pop3ConData->pause_for_read = false; + break; + } + + m_pop3ConData->pause_for_read = false; + // we are already connected so just go on and send the username + if (m_prefAuthMethods == POP3_HAS_AUTH_USER) + { + m_currentAuthMethod = POP3_HAS_AUTH_USER; + m_pop3ConData->next_state = POP3_SEND_USERNAME; + } + else + { + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + m_pop3ConData->next_state = POP3_SEND_AUTH; + else + m_pop3ConData->next_state = POP3_SEND_CAPA; + } + break; + } + + + case POP3_START_CONNECT: + { + m_pop3ConData->next_state = POP3_FINISH_CONNECT; + m_pop3ConData->pause_for_read = false; + break; + } + + case POP3_FINISH_CONNECT: + { + m_pop3ConData->pause_for_read = false; + m_pop3ConData->next_state = POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE; + break; + } + + case POP3_WAIT_FOR_RESPONSE: + status = WaitForResponse(aInputStream, aLength); + break; + + case POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE: + { + status = WaitForStartOfConnectionResponse(aInputStream, aLength); + + if(status) + { + if (m_prefAuthMethods == POP3_HAS_AUTH_USER) + { + m_currentAuthMethod = POP3_HAS_AUTH_USER; + m_pop3ConData->next_state = POP3_SEND_USERNAME; + } + else + { + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + m_pop3ConData->next_state = POP3_SEND_AUTH; + else + m_pop3ConData->next_state = POP3_SEND_CAPA; + } + } + + break; + } + + case POP3_SEND_AUTH: + status = SendAuth(); + break; + + case POP3_AUTH_RESPONSE: + status = AuthResponse(aInputStream, aLength); + break; + + case POP3_SEND_CAPA: + status = SendCapa(); + break; + + case POP3_CAPA_RESPONSE: + status = CapaResponse(aInputStream, aLength); + break; + + case POP3_TLS_RESPONSE: + status = SendTLSResponse(); + break; + + case POP3_PROCESS_AUTH: + status = ProcessAuth(); + break; + + case POP3_NEXT_AUTH_STEP: + status = NextAuthStep(); + break; + + case POP3_AUTH_LOGIN: + status = AuthLogin(); + break; + + case POP3_AUTH_LOGIN_RESPONSE: + status = AuthLoginResponse(); + break; + + case POP3_AUTH_NTLM: + status = AuthNtlm(); + break; + + case POP3_AUTH_NTLM_RESPONSE: + status = AuthNtlmResponse(); + break; + + case POP3_AUTH_GSSAPI: + status = AuthGSSAPI(); + break; + + case POP3_AUTH_GSSAPI_FIRST: + UpdateStatus(u"hostContact"); + status = AuthGSSAPIResponse(true); + break; + + case POP3_AUTH_GSSAPI_STEP: + status = AuthGSSAPIResponse(false); + break; + + case POP3_SEND_USERNAME: + if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_USERNAME))) + status = -1; + break; + + case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME: + status = -1; + break; + + case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME: + UpdateStatus(u"hostContact"); + status = SendUsername(); + break; + + case POP3_SEND_PASSWORD: + if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD))) + status = -1; + break; + + case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD: + status = SendPassword(); + break; + + case POP3_SEND_GURL: + status = SendGurl(); + break; + + case POP3_GURL_RESPONSE: + status = GurlResponse(); + break; + + case POP3_SEND_STAT: + status = SendStat(); + break; + + case POP3_GET_STAT: + status = GetStat(); + break; + + case POP3_SEND_LIST: + status = SendList(); + break; + + case POP3_GET_LIST: + status = GetList(aInputStream, aLength); + break; + + case POP3_SEND_UIDL_LIST: + status = SendUidlList(); + break; + + case POP3_GET_UIDL_LIST: + status = GetUidlList(aInputStream, aLength); + break; + + case POP3_SEND_XTND_XLST_MSGID: + status = SendXtndXlstMsgid(); + break; + + case POP3_GET_XTND_XLST_MSGID: + status = GetXtndXlstMsgid(aInputStream, aLength); + break; + + case POP3_GET_MSG: + status = GetMsg(); + break; + + case POP3_SEND_TOP: + status = SendTop(); + break; + + case POP3_TOP_RESPONSE: + status = TopResponse(aInputStream, aLength); + break; + + case POP3_SEND_XSENDER: + status = SendXsender(); + break; + + case POP3_XSENDER_RESPONSE: + status = XsenderResponse(); + break; + + case POP3_SEND_RETR: + status = SendRetr(); + break; + + case POP3_RETR_RESPONSE: + status = RetrResponse(aInputStream, aLength); + break; + + case POP3_SEND_DELE: + status = SendDele(); + break; + + case POP3_DELE_RESPONSE: + status = DeleResponse(); + break; + + case POP3_SEND_QUIT: + /* attempt to send a server quit command. Since this means + everything went well, this is a good time to update the + status file and the FE's biff state. + */ + if (!m_pop3ConData->only_uidl) + { + /* update old style biff */ + if (!m_pop3ConData->only_check_for_new_mail) + { + /* We don't want to pop up a warning message any more (see + bug 54116), so instead we put the "no new messages" or + "retrieved x new messages" + in the status line. Unfortunately, this tends to be running + in a progress pane, so we try to get the real pane and + show the message there. */ + + if (m_totalDownloadSize <= 0) + { + UpdateStatus(u"noNewMessages"); + /* There are no new messages. */ + } + else + { + nsString statusString; + nsresult rv = FormatCounterString(NS_LITERAL_STRING("receivedMsgs"), + m_pop3ConData->real_new_counter - 1, + m_pop3ConData->really_new_messages, + statusString); + if (NS_SUCCEEDED(rv)) + UpdateStatusWithString(statusString.get()); + } + } + } + + status = Pop3SendData("QUIT" CRLF); + m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE; + m_pop3ConData->next_state_after_response = POP3_QUIT_RESPONSE; + break; + + case POP3_QUIT_RESPONSE: + if(m_pop3ConData->command_succeeded) + { + /* the QUIT succeeded. We can now flush the state in popstate.dat which + keeps track of any uncommitted DELE's */ + + /* clear the hash of all our uncommitted deletes */ + if (!m_pop3ConData->leave_on_server && m_pop3ConData->newuidl) + { + PL_HashTableEnumerateEntries(m_pop3ConData->newuidl, + net_pop3_remove_messages_marked_delete, + (void *)m_pop3ConData); + } + m_pop3ConData->next_state = POP3_DONE; + } + else + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + } + break; + + case POP3_DONE: + CommitState(false); + m_pop3ConData->urlStatus = NS_OK; + urlStatusSet = true; + m_pop3ConData->next_state = POP3_FREE; + break; + + case POP3_ERROR_DONE: + /* write out the state */ + if(m_pop3ConData->list_done) + CommitState(true); + + if(m_pop3ConData->msg_closure) + { + m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr); + m_pop3ConData->msg_closure = NULL; + m_nsIPop3Sink->AbortMailDelivery(this); + } + + if(m_pop3ConData->msg_del_started) + { + nsString statusString; + nsresult rv = FormatCounterString(NS_LITERAL_STRING("receivedMsgs"), + m_pop3ConData->real_new_counter - 1, + m_pop3ConData->really_new_messages, + statusString); + if (NS_SUCCEEDED(rv)) + UpdateStatusWithString(statusString.get()); + + NS_ASSERTION (!TestFlag(POP3_PASSWORD_FAILED), "POP3_PASSWORD_FAILED set when del_started"); + m_nsIPop3Sink->AbortMailDelivery(this); + } + { // this brace is to avoid compiler error about vars in switch case. + nsCOMPtr<nsIMsgWindow> msgWindow; + + if (mailnewsurl) + mailnewsurl->GetMsgWindow(getter_AddRefs(msgWindow)); + // no msgWindow means no re-prompt, so treat as error. + if (TestFlag(POP3_PASSWORD_FAILED) && msgWindow) + { + // We get here because the password was wrong. + if (!m_socketIsOpen && mailnewsurl) + { + // The server dropped the connection, so we're going + // to re-run the url. + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("need to rerun url because connection dropped during auth"))); + m_needToRerunUrl = true; + return NS_OK; + } + m_pop3ConData->next_state = POP3_READ_PASSWORD; + m_pop3ConData->command_succeeded = true; + status = 0; + break; + } + else + /* Else we got a "real" error, so finish up. */ + m_pop3ConData->next_state = POP3_FREE; + } + m_pop3ConData->urlStatus = NS_ERROR_FAILURE; + urlStatusSet = true; + m_pop3ConData->pause_for_read = false; + break; + + case POP3_FREE: + { + UpdateProgressPercent(0,0); // clear out the progress meter + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Clearing server busy in POP3_FREE"))); + server->SetServerBusy(false); // the server is now not busy + } + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Clearing running protocol in POP3_FREE"))); + CloseSocket(); + m_pop3Server->SetRunningProtocol(nullptr); + if (mailnewsurl && urlStatusSet) + mailnewsurl->SetUrlState(false, m_pop3ConData->urlStatus); + + m_url = nullptr; + return NS_OK; + } + default: + NS_ERROR("Got to unexpected state in nsPop3Protocol::ProcessProtocolState"); + status = -1; + } /* end switch */ + + if((status < 0) && m_pop3ConData->next_state != POP3_FREE) + { + m_pop3ConData->pause_for_read = false; + m_pop3ConData->next_state = POP3_ERROR_DONE; + } + + } /* end while */ + + return NS_OK; + +} + +NS_IMETHODIMP nsPop3Protocol::MarkMessages(nsTArray<Pop3UidlEntry*> *aUIDLArray) +{ + NS_ENSURE_ARG_POINTER(aUIDLArray); + uint32_t count = aUIDLArray->Length(); + + for (uint32_t i = 0; i < count; i++) + { + bool changed; + if (m_pop3ConData->newuidl) + MarkMsgInHashTable(m_pop3ConData->newuidl, aUIDLArray->ElementAt(i), &changed); + if (m_pop3ConData->uidlinfo) + MarkMsgInHashTable(m_pop3ConData->uidlinfo->hash, aUIDLArray->ElementAt(i), &changed); + } + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::CheckMessage(const char *aUidl, bool *aBool) +{ + Pop3UidlEntry *uidlEntry = nullptr; + + if (aUidl) + { + if (m_pop3ConData->newuidl) + uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->newuidl, aUidl); + else if (m_pop3ConData->uidlinfo) + uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, aUidl); + } + + *aBool = uidlEntry ? true : false; + return NS_OK; +} + + +/* Function for finding an APOP Timestamp and simple check + it for its validity. If returning NS_OK m_ApopTimestamp + contains the validated substring of m_commandResponse. */ +nsresult nsPop3Protocol::GetApopTimestamp() +{ + int32_t startMark = m_commandResponse.Length(), endMark = -1; + + while (true) + { + // search for previous < + if ((startMark = MsgRFindChar(m_commandResponse, '<', startMark - 1)) < 0) + return NS_ERROR_FAILURE; + + // search for next > + if ((endMark = m_commandResponse.FindChar('>', startMark)) < 0) + continue; + + // look for an @ between start and end as a raw test + int32_t at = m_commandResponse.FindChar('@', startMark); + if (at < 0 || at >= endMark) + continue; + + // now test if sub only consists of chars in ASCII range + nsCString sub(Substring(m_commandResponse, startMark, endMark - startMark + 1)); + if (NS_IsAscii(sub.get())) + { + // set m_ApopTimestamp to the validated substring + m_ApopTimestamp.Assign(sub); + break; + } + } + + return NS_OK; +} diff --git a/mailnews/local/src/nsPop3Protocol.h b/mailnews/local/src/nsPop3Protocol.h new file mode 100644 index 000000000..a937427d1 --- /dev/null +++ b/mailnews/local/src/nsPop3Protocol.h @@ -0,0 +1,402 @@ +/* -*- 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/. */ + +#ifndef nsPop3Protocol_h__ +#define nsPop3Protocol_h__ + +#include "mozilla/Attributes.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIPop3URL.h" +#include "nsIPop3Sink.h" +#include "nsMsgLineBuffer.h" +#include "nsMsgProtocol.h" +#include "nsIPop3Protocol.h" +#include "MailNewsTypes.h" +#include "nsIStringBundle.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsIAuthModule.h" +#include "nsITimer.h" +#include "nsIMsgAsyncPrompter.h" + +#include "prerror.h" +#include "plhash.h" +#include "nsCOMPtr.h" + +/* A more guaranteed way of making sure that we never get duplicate messages +is to always get each message's UIDL (if the server supports it) +and use these for storing up deletes which were not committed on the +server. Based on our experience, it looks like we do NOT need to +do this (it has performance tradeoffs, etc.). To turn it on, three +things need to happen: #define POP_ALWAYS_USE_UIDL_FOR_DUPLICATES, verify that +the uidl's are correctly getting added when the delete response is received, +and change the POP3_QUIT_RESPONSE state to flush the newly committed deletes. */ + +/* + * Cannot have the following line uncommented. It is defined. + * #ifdef POP_ALWAYS_USE_UIDL_FOR_DUPLICATES will always be evaluated + * as true. + * +#define POP_ALWAYS_USE_UIDL_FOR_DUPLICATES 0 + * + */ + +#define MK_OUT_OF_MEMORY -207 +#define MK_POP3_PASSWORD_UNDEFINED -313 +#define XP_NO_ANSWER 14401 +#define XP_THE_PREVIOUSLY_ENTERED_PASSWORD_IS_INVALID_ETC 14405 +#define XP_PASSWORD_FOR_POP3_USER 14590 + +#define OUTPUT_BUFFER_SIZE 8192 // maximum size of command string + +/* structure to hold data pertaining to the active state of + * a transfer in progress. + * + */ + +enum Pop3CapabilityEnum { + POP3_CAPABILITY_UNDEFINED = 0x00000000, + POP3_HAS_XSENDER = 0x00000001, + POP3_GURL_UNDEFINED = 0x00000002, + POP3_HAS_GURL = 0x00000004, + POP3_UIDL_UNDEFINED = 0x00000008, + POP3_HAS_UIDL = 0x00000010, + POP3_XTND_XLST_UNDEFINED = 0x00000020, + POP3_HAS_XTND_XLST = 0x00000040, + POP3_TOP_UNDEFINED = 0x00000080, + POP3_HAS_TOP = 0x00000100, + POP3_AUTH_MECH_UNDEFINED = 0x00000200, + POP3_HAS_AUTH_USER = 0x00000400, + POP3_HAS_AUTH_LOGIN = 0x00000800, + POP3_HAS_AUTH_PLAIN = 0x00001000, + POP3_HAS_AUTH_CRAM_MD5 = 0x00002000, + POP3_HAS_AUTH_APOP = 0x00004000, + POP3_HAS_AUTH_NTLM = 0x00008000, + POP3_HAS_AUTH_MSN = 0x00010000, + POP3_HAS_RESP_CODES = 0x00020000, + POP3_HAS_AUTH_RESP_CODE = 0x00040000, + POP3_HAS_STLS = 0x00080000, + POP3_HAS_AUTH_GSSAPI = 0x00100000 +}; + +// TODO use value > 0? +#define POP3_HAS_AUTH_NONE 0 +#define POP3_HAS_AUTH_ANY 0x00001C00 +#define POP3_HAS_AUTH_ANY_SEC 0x0011E000 + +enum Pop3StatesEnum { + POP3_READ_PASSWORD, // 0 + // + POP3_START_CONNECT, // 1 + POP3_FINISH_CONNECT, // 2 + POP3_WAIT_FOR_RESPONSE, // 3 + POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE, // 4 + POP3_SEND_USERNAME, // 5 + + POP3_SEND_PASSWORD, // 6 + POP3_SEND_STAT, // 7 + POP3_GET_STAT, // 8 + POP3_SEND_LIST, // 9 + POP3_GET_LIST, // 10 + + POP3_SEND_UIDL_LIST, // 11 + POP3_GET_UIDL_LIST, // 12 + POP3_SEND_XTND_XLST_MSGID, // 13 + POP3_GET_XTND_XLST_MSGID, // 14 + POP3_GET_MSG, // 15 + + POP3_SEND_TOP, // 16 + POP3_TOP_RESPONSE, // 17 + POP3_SEND_RETR, // 18 + POP3_RETR_RESPONSE, // 19 + POP3_SEND_DELE, // 20 + + POP3_DELE_RESPONSE, // 21 + POP3_SEND_QUIT, // 22 + POP3_DONE, // 23 + POP3_ERROR_DONE, // 24 + POP3_FREE, // 25 + POP3_SEND_AUTH, // 26 + POP3_AUTH_RESPONSE, // 27 + POP3_SEND_CAPA, // 28 + POP3_CAPA_RESPONSE, // 29 + POP3_PROCESS_AUTH, // 30 + POP3_NEXT_AUTH_STEP, // 31 + + POP3_AUTH_LOGIN, // 32 + POP3_AUTH_LOGIN_RESPONSE, // 33 + POP3_AUTH_NTLM, // 34 + POP3_AUTH_NTLM_RESPONSE, // 35 + POP3_SEND_XSENDER, // 36 + POP3_XSENDER_RESPONSE, // 37 + POP3_SEND_GURL, // 38 + + POP3_GURL_RESPONSE, // 39 + POP3_QUIT_RESPONSE, // 40 + POP3_TLS_RESPONSE, // 41 + + POP3_AUTH_GSSAPI, // 42 + POP3_AUTH_GSSAPI_FIRST, // 43 + POP3_AUTH_GSSAPI_STEP, // 44 + + /** + * Async wait to obtain the password and deal with the result. + * The *PREOBTAIN* states are used for where we try and get the password + * before we've initiated a connection to the server. + */ + POP3_OBTAIN_PASSWORD_EARLY, // 45 + POP3_FINISH_OBTAIN_PASSWORD_EARLY, // 46 + POP3_OBTAIN_PASSWORD_BEFORE_USERNAME, // 47 + POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME,// 48 + POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD, // 49 + POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD // 50 +}; + + +#define KEEP 'k' /* If we want to keep this item on server. */ +#define DELETE_CHAR 'd' /* If we want to delete this item. */ +#define TOO_BIG 'b' /* item left on server because it was too big */ +#define FETCH_BODY 'f' /* Fetch full body of a partial msg */ + +typedef struct Pop3UidlEntry { /* information about this message */ + char* uidl; + char status; // KEEP=='k', DELETE='d' TOO_BIG='b' FETCH_BODY='f' + uint32_t dateReceived; // time message received, used for aging +} Pop3UidlEntry; + +typedef struct Pop3UidlHost { + char* host; + char* user; + PLHashTable * hash; + Pop3UidlEntry* uidlEntries; + struct Pop3UidlHost* next; +} Pop3UidlHost; + +typedef struct Pop3MsgInfo { + int32_t msgnum; + int32_t size; + char* uidl; +} Pop3MsgInfo; + +typedef struct _Pop3ConData { + bool leave_on_server; /* Whether we're supposed to leave messages + on server. */ + bool headers_only; /* Whether to just fetch headers on initial + downloads. */ + int32_t size_limit; /* Leave messages bigger than this on the + server and only download a partial + message. */ + uint32_t capability_flags; /* What capability this server has? */ + + Pop3StatesEnum next_state; /* the next state or action to be taken */ + Pop3StatesEnum next_state_after_response; + bool pause_for_read; /* Pause now for next read? */ + + bool command_succeeded; /* did the last command succeed? */ + bool list_done; /* did we get the complete list of msgIDs? */ + int32_t first_msg; + + uint32_t obuffer_size; + uint32_t obuffer_fp; + + int32_t really_new_messages; + int32_t real_new_counter; + int32_t number_of_messages; + Pop3MsgInfo *msg_info; /* Message sizes and uidls (used only if we + are playing games that involve leaving + messages on the server). */ + int32_t last_accessed_msg; + int32_t cur_msg_size; + bool truncating_cur_msg; /* are we using top and uidl? */ + bool msg_del_started; /* True if MSG_BeginMailDel... + * called + */ + bool only_check_for_new_mail; + nsMsgBiffState biffstate; /* If just checking for, what the answer is. */ + + bool verify_logon; /* true if we're just seeing if we can logon */ + + void *msg_closure; + + bool graph_progress_bytes_p; /* whether we should display info about + the bytes transferred (usually we + display info about the number of + messages instead.) */ + + Pop3UidlHost *uidlinfo; + PLHashTable *newuidl; + char *only_uidl; /* If non-NULL, then load only this UIDL. */ + + bool get_url; + bool seenFromHeader; + int32_t parsed_bytes; + int32_t pop3_size; + bool dot_fix; + bool assumed_end; + nsresult urlStatus; +} Pop3ConData; + +// State Flags (Note, I use the word state in terms of storing +// state information about the connection (authentication, have we sent +// commands, etc. I do not intend it to refer to protocol state) +#define POP3_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */ +#define POP3_PASSWORD_FAILED 0x00000002 +#define POP3_STOPLOGIN 0x00000004 /* error loging in, so stop here */ +#define POP3_AUTH_FAILURE 0x00000008 /* extended code said authentication failed */ + + +class nsPop3Protocol : public nsMsgProtocol, + public nsIPop3Protocol, + public nsIMsgAsyncPromptListener +{ +public: + nsPop3Protocol(nsIURI* aURL); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPOP3PROTOCOL + NS_DECL_NSIMSGASYNCPROMPTLISTENER + + nsresult Initialize(nsIURI * aURL); + virtual nsresult LoadUrl(nsIURI *aURL, nsISupports * aConsumer = nullptr) override; + void Cleanup(); + + const char* GetUsername() { return m_username.get(); } + void SetUsername(const char* name); + + nsresult StartGetAsyncPassword(Pop3StatesEnum aNextState); + + NS_IMETHOD OnTransportStatus(nsITransport *transport, nsresult status, int64_t progress, int64_t progressMax) override; + NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports * aContext, nsresult aStatus) override; + NS_IMETHOD Cancel(nsresult status) override; + + static void MarkMsgInHashTable(PLHashTable *hashTable, const Pop3UidlEntry *uidl, + bool *changed); + + static nsresult MarkMsgForHost(const char *hostName, const char *userName, + nsIFile *mailDirectory, + nsTArray<Pop3UidlEntry*> &UIDLArray); +private: + virtual ~nsPop3Protocol(); + nsCString m_ApopTimestamp; + nsCOMPtr<nsIStringBundle> mLocalBundle; + + nsCString m_username; + nsCString m_senderInfo; + nsCString m_commandResponse; + nsCString m_GSSAPICache; + + // Used for asynchronous password prompts to store the password temporarily. + nsCString m_passwordResult; + + // progress state information + void UpdateProgressPercent(int64_t totalDone, int64_t total); + void UpdateStatus(const char16_t *aStatusName); + void UpdateStatusWithString(const char16_t *aString); + nsresult FormatCounterString(const nsString &stringName, + uint32_t count1, + uint32_t count2, + nsString &resultString); + + int32_t m_bytesInMsgReceived; + int64_t m_totalFolderSize; + int64_t m_totalDownloadSize; /* Number of bytes we're going to + download. Might be much less + than the total_folder_size. */ + int64_t m_totalBytesReceived; // total # bytes received for the connection + + virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, + uint64_t sourceOffset, uint32_t length) override; + virtual int32_t Pop3SendData(const char * dataBuffer, bool aSuppressLogging = false); + + virtual const char* GetType() override {return "pop3";} + + nsCOMPtr<nsIURI> m_url; + nsCOMPtr<nsIPop3Sink> m_nsIPop3Sink; + nsCOMPtr<nsIPop3IncomingServer> m_pop3Server; + + nsMsgLineStreamBuffer * m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream + Pop3ConData* m_pop3ConData; + void FreeMsgInfo(); + void Abort(); + + bool m_tlsEnabled; + int32_t m_socketType; + bool m_password_already_sent; + bool m_needToRerunUrl; + + void SetCapFlag(uint32_t flag); + void ClearCapFlag(uint32_t flag); + bool TestCapFlag(uint32_t flag); + uint32_t GetCapFlags(); + + void InitPrefAuthMethods(int32_t authMethodPrefValue); + nsresult ChooseAuthMethod(); + void MarkAuthMethodAsFailed(int32_t failedAuthMethod); + void ResetAuthMethods(); + int32_t m_prefAuthMethods; // set of capability flags for auth methods + int32_t m_failedAuthMethods; // ditto + int32_t m_currentAuthMethod; // exactly one capability flag, or 0 + + int32_t m_listpos; + + nsresult HandleLine(char *line, uint32_t line_length); + + nsresult GetApopTimestamp(); + + ////////////////////////////////////////////////////////////////////////////////////////// + // Begin Pop3 protocol state handlers + ////////////////////////////////////////////////////////////////////////////////////////// + int32_t WaitForStartOfConnectionResponse(nsIInputStream* inputStream, + uint32_t length); + int32_t WaitForResponse(nsIInputStream* inputStream, + uint32_t length); + int32_t Error(const char* err_code, const char16_t **params = nullptr, + uint32_t length = 0); + int32_t SendAuth(); + int32_t AuthResponse(nsIInputStream* inputStream, uint32_t length); + int32_t SendCapa(); + int32_t CapaResponse(nsIInputStream* inputStream, uint32_t length); + int32_t SendTLSResponse(); + int32_t ProcessAuth(); + int32_t NextAuthStep(); + int32_t AuthLogin(); + int32_t AuthLoginResponse(); + int32_t AuthNtlm(); + int32_t AuthNtlmResponse(); + int32_t AuthGSSAPI(); + int32_t AuthGSSAPIResponse(bool first); + int32_t SendUsername(); + int32_t SendPassword(); + int32_t SendStatOrGurl(bool sendStat); + int32_t SendStat(); + int32_t GetStat(); + int32_t SendGurl(); + int32_t GurlResponse(); + int32_t SendList(); + int32_t GetList(nsIInputStream* inputStream, uint32_t length); + int32_t HandleNoUidListAvailable(); + int32_t SendXtndXlstMsgid(); + int32_t GetXtndXlstMsgid(nsIInputStream* inputStream, uint32_t length); + int32_t SendUidlList(); + int32_t GetUidlList(nsIInputStream* inputStream, uint32_t length); + int32_t GetMsg(); + int32_t SendTop(); + int32_t SendXsender(); + int32_t XsenderResponse(); + int32_t SendRetr(); + + int32_t RetrResponse(nsIInputStream* inputStream, uint32_t length); + int32_t TopResponse(nsIInputStream* inputStream, uint32_t length); + int32_t SendDele(); + int32_t DeleResponse(); + int32_t CommitState(bool remove_last_entry); + + Pop3StatesEnum GetNextPasswordObtainState(); + nsresult RerunUrl(); +}; + +#endif /* nsPop3Protocol_h__ */ diff --git a/mailnews/local/src/nsPop3Service.cpp b/mailnews/local/src/nsPop3Service.cpp new file mode 100644 index 000000000..d0177b559 --- /dev/null +++ b/mailnews/local/src/nsPop3Service.cpp @@ -0,0 +1,711 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... + +#include "nsPop3Service.h" +#include "nsIMsgIncomingServer.h" +#include "nsIPop3IncomingServer.h" + +#include "nsPop3URL.h" +#include "nsPop3Sink.h" +#include "nsPop3Protocol.h" +#include "nsMsgLocalCID.h" +#include "nsMsgBaseCID.h" +#include "nsCOMPtr.h" +#include "nsIMsgWindow.h" +#include "nsINetUtil.h" + +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsIDirectoryService.h" +#include "nsMailDirServiceDefs.h" +#include "prprf.h" +#include "nsMsgUtils.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsLocalMailFolder.h" +#include "nsIMailboxUrl.h" +#include "nsIPrompt.h" +#include "nsINetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Services.h" + +#define PREF_MAIL_ROOT_POP3 "mail.root.pop3" // old - for backward compatibility only +#define PREF_MAIL_ROOT_POP3_REL "mail.root.pop3-rel" + +static NS_DEFINE_CID(kPop3UrlCID, NS_POP3URL_CID); +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +nsPop3Service::nsPop3Service() +{ +} + +nsPop3Service::~nsPop3Service() +{} + +NS_IMPL_ISUPPORTS(nsPop3Service, + nsIPop3Service, + nsIProtocolHandler, + nsIMsgProtocolInfo) + +NS_IMETHODIMP nsPop3Service::CheckForNewMail(nsIMsgWindow* aMsgWindow, + nsIUrlListener *aUrlListener, + nsIMsgFolder *aInbox, + nsIPop3IncomingServer *aPopServer, + nsIURI **aURL) +{ + return GetMail(false /* don't download, just check */, + aMsgWindow, aUrlListener, aInbox, aPopServer, aURL); +} + + +nsresult nsPop3Service::GetNewMail(nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener, + nsIMsgFolder *aInbox, + nsIPop3IncomingServer *aPopServer, + nsIURI **aURL) +{ + return GetMail(true /* download */, + aMsgWindow, aUrlListener, aInbox, aPopServer, aURL); +} + +nsresult nsPop3Service::GetMail(bool downloadNewMail, + nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener, + nsIMsgFolder *aInbox, + nsIPop3IncomingServer *aPopServer, + nsIURI **aURL) +{ + + NS_ENSURE_ARG_POINTER(aInbox); + int32_t popPort = -1; + + nsCOMPtr<nsIMsgIncomingServer> server; + nsCOMPtr<nsIURI> url; + + server = do_QueryInterface(aPopServer); + NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder = do_QueryInterface(aInbox); + if (destLocalFolder) + { + // We don't know the needed size yet, so at least check + // if there is some free space (1MB) in the message store. + bool destFolderTooBig; + destLocalFolder->WarnIfLocalFileTooBig(aMsgWindow, 0xFFFF, &destFolderTooBig); + if (destFolderTooBig) + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + nsCString popHost; + nsCString popUser; + nsresult rv = server->GetHostName(popHost); + NS_ENSURE_SUCCESS(rv, rv); + if (popHost.IsEmpty()) + return NS_MSG_INVALID_OR_MISSING_SERVER; + + rv = server->GetPort(&popPort); + NS_ENSURE_SUCCESS(rv, rv); + + rv = server->GetUsername(popUser); + NS_ENSURE_SUCCESS(rv, rv); + if (popUser.IsEmpty()) + return NS_MSG_SERVER_USERNAME_MISSING; + + nsCString escapedUsername; + MsgEscapeString(popUser, nsINetUtil::ESCAPE_XALPHAS, escapedUsername); + + if (NS_SUCCEEDED(rv) && aPopServer) + { + // now construct a pop3 url... + // we need to escape the username because it may contain + // characters like / % or @ + char * urlSpec = (downloadNewMail) + ? PR_smprintf("pop3://%s@%s:%d", escapedUsername.get(), popHost.get(), popPort) + : PR_smprintf("pop3://%s@%s:%d/?check", escapedUsername.get(), popHost.get(), popPort); + rv = BuildPop3Url(urlSpec, aInbox, aPopServer, aUrlListener, getter_AddRefs(url), aMsgWindow); + PR_smprintf_free(urlSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ENSURE_TRUE(url, rv); + + if (NS_SUCCEEDED(rv)) + rv = RunPopUrl(server, url); + + if (aURL) // we already have a ref count on pop3url... + NS_IF_ADDREF(*aURL = url); + + return rv; +} + +NS_IMETHODIMP nsPop3Service::VerifyLogon(nsIMsgIncomingServer *aServer, + nsIUrlListener *aUrlListener, + nsIMsgWindow *aMsgWindow, + nsIURI **aURL) +{ + NS_ENSURE_ARG_POINTER(aServer); + nsCString popHost; + nsCString popUser; + int32_t popPort = -1; + + nsresult rv = aServer->GetHostName(popHost); + NS_ENSURE_SUCCESS(rv, rv); + + if (popHost.IsEmpty()) + return NS_MSG_INVALID_OR_MISSING_SERVER; + + rv = aServer->GetPort(&popPort); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aServer->GetUsername(popUser); + NS_ENSURE_SUCCESS(rv, rv); + + if (popUser.IsEmpty()) + return NS_MSG_SERVER_USERNAME_MISSING; + + nsCString escapedUsername; + MsgEscapeString(popUser, nsINetUtil::ESCAPE_XALPHAS, escapedUsername); + + nsCOMPtr<nsIPop3IncomingServer> popServer = do_QueryInterface(aServer, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // now construct a pop3 url... + // we need to escape the username because it may contain + // characters like / % or @ + char *urlSpec = PR_smprintf("pop3://%s@%s:%d/?verifyLogon", + escapedUsername.get(), popHost.get(), popPort); + NS_ENSURE_TRUE(urlSpec, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr<nsIURI> url; + rv = BuildPop3Url(urlSpec, nullptr, popServer, aUrlListener, + getter_AddRefs(url), aMsgWindow); + PR_smprintf_free(urlSpec); + + if (NS_SUCCEEDED(rv) && url) + { + rv = RunPopUrl(aServer, url); + if (NS_SUCCEEDED(rv) && aURL) + url.forget(aURL); + } + + return rv; +} + +nsresult nsPop3Service::BuildPop3Url(const char *urlSpec, + nsIMsgFolder *inbox, + nsIPop3IncomingServer *server, + nsIUrlListener *aUrlListener, + nsIURI **aUrl, + nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + + nsPop3Sink *pop3Sink = new nsPop3Sink(); + + NS_ENSURE_TRUE(pop3Sink, NS_ERROR_OUT_OF_MEMORY); + + pop3Sink->SetPopServer(server); + pop3Sink->SetFolder(inbox); + + // now create a pop3 url and a protocol instance to run the url.... + nsCOMPtr<nsIPop3URL> pop3Url = do_CreateInstance(kPop3UrlCID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + pop3Url->SetPop3Sink(pop3Sink); + + rv = CallQueryInterface(pop3Url, aUrl); + NS_ENSURE_SUCCESS(rv,rv); + + rv = (*aUrl)->SetSpec(nsDependentCString(urlSpec)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(pop3Url); + if (mailnewsurl) + { + if (aUrlListener) + mailnewsurl->RegisterListener(aUrlListener); + if (aMsgWindow) + mailnewsurl->SetMsgWindow(aMsgWindow); + } + + return rv; +} + +nsresult nsPop3Service::RunPopUrl(nsIMsgIncomingServer *aServer, nsIURI *aUrlToRun) +{ + + NS_ENSURE_ARG_POINTER(aServer); + NS_ENSURE_ARG_POINTER(aUrlToRun); + + nsCString userName; + + // load up required server information + // we store the username unescaped in the server + // so there is no need to unescape it + nsresult rv = aServer->GetRealUsername(userName); + + // find out if the server is busy or not...if the server is busy, we are + // *NOT* going to run the url + bool serverBusy = false; + rv = aServer->GetServerBusy(&serverBusy); + NS_ENSURE_SUCCESS(rv, rv); + if (!serverBusy) + { + RefPtr<nsPop3Protocol> protocol = new nsPop3Protocol(aUrlToRun); + if (protocol) + { + // the protocol stores the unescaped username, so there is no need to escape it. + protocol->SetUsername(userName.get()); + rv = protocol->LoadUrl(aUrlToRun); + if (NS_FAILED(rv)) + aServer->SetServerBusy(false); + } + } + else + { + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(aUrlToRun); + if (url) + AlertServerBusy(url); + rv = NS_ERROR_FAILURE; + } + return rv; +} + + +NS_IMETHODIMP nsPop3Service::GetScheme(nsACString &aScheme) +{ + aScheme.AssignLiteral("pop3"); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::GetDefaultPort(int32_t *aDefaultPort) +{ + NS_ENSURE_ARG_POINTER(aDefaultPort); + *aDefaultPort = nsIPop3URL::DEFAULT_POP3_PORT; + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + *_retval = true; // allow pop on any port + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::GetDefaultDoBiff(bool *aDoBiff) +{ + NS_ENSURE_ARG_POINTER(aDoBiff); + // by default, do biff for POP3 servers + *aDoBiff = true; + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::GetProtocolFlags(uint32_t *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = URI_NORELATIVE | URI_DANGEROUS_TO_LOAD | ALLOWS_PROXY | + URI_FORBIDS_COOKIE_ACCESS; + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::NewURI(const nsACString &aSpec, + const char *aOriginCharset, // ignored + nsIURI *aBaseURI, + nsIURI **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsAutoCString folderUri(aSpec); + nsCOMPtr<nsIRDFResource> resource; + int32_t offset = folderUri.FindChar('?'); + if (offset != kNotFound) + folderUri.SetLength(offset); + + const char *uidl = PL_strstr(nsCString(aSpec).get(), "uidl="); + NS_ENSURE_TRUE(uidl, NS_ERROR_FAILURE); + + nsresult rv; + + nsCOMPtr<nsIRDFService> rdfService(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rdfService->GetResource(folderUri, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(resource, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> server; + + nsLocalFolderScanState folderScanState; + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder); + nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_QueryInterface(aBaseURI); + + if (mailboxUrl && localFolder) + { + rv = localFolder->GetFolderScanState(&folderScanState); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsMsgKey msgKey; + mailboxUrl->GetMessageKey(&msgKey); + folder->GetMessageHeader(msgKey, getter_AddRefs(msgHdr)); + // we do this to get the account key + if (msgHdr) + localFolder->GetUidlFromFolder(&folderScanState, msgHdr); + if (!folderScanState.m_accountKey.IsEmpty()) + { + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (accountManager) + { + nsCOMPtr<nsIMsgAccount> account; + accountManager->GetAccount(folderScanState.m_accountKey, getter_AddRefs(account)); + if (account) + account->GetIncomingServer(getter_AddRefs(server)); + } + } + } + + if (!server) + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPop3IncomingServer> popServer = do_QueryInterface(server,&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString hostname; + nsCString username; + server->GetHostName(hostname); + server->GetUsername(username); + + int32_t port; + server->GetPort(&port); + if (port == -1) port = nsIPop3URL::DEFAULT_POP3_PORT; + + // we need to escape the username because it may contain + // characters like / % or @ + nsCString escapedUsername; + MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername); + + nsAutoCString popSpec("pop://"); + popSpec += escapedUsername; + popSpec += "@"; + popSpec += hostname; + popSpec += ":"; + popSpec.AppendInt(port); + popSpec += "?"; + popSpec += uidl; + nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(folder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = BuildPop3Url(popSpec.get(), folder, popServer, + urlListener, _retval, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(*_retval, &rv); + if (NS_SUCCEEDED(rv)) + { + // escape the username before we call SetUsername(). we do this because GetUsername() + // will unescape the username + mailnewsurl->SetUsername(escapedUsername); + } + + nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(mailnewsurl, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString messageUri (aSpec); + if (!strncmp(messageUri.get(), "mailbox:", 8)) + messageUri.Replace(0, 8, "mailbox-message:"); + offset = messageUri.Find("?number="); + if (offset != kNotFound) + messageUri.Replace(offset, 8, "#"); + offset = messageUri.FindChar('&'); + if (offset != kNotFound) + messageUri.SetLength(offset); + popurl->SetMessageUri(messageUri.get()); + nsCOMPtr<nsIPop3Sink> pop3Sink; + rv = popurl->GetPop3Sink(getter_AddRefs(pop3Sink)); + NS_ENSURE_SUCCESS(rv, rv); + + pop3Sink->SetBuildMessageUri(true); + + return NS_OK; +} + +void nsPop3Service::AlertServerBusy(nsIMsgMailNewsUrl *url) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return; + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIPrompt> dialog; + rv = url->GetMsgWindow(getter_AddRefs(msgWindow)); //it is ok to have null msgWindow, for example when biffing + if (NS_FAILED(rv) || !msgWindow) + return; + + rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsString accountName; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = url->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS_VOID(rv); + rv = server->GetPrettyName(accountName); + NS_ENSURE_SUCCESS_VOID(rv); + + const char16_t *params[] = { accountName.get() }; + nsString alertString; + nsString dialogTitle; + bundle->FormatStringFromName( + u"pop3ServerBusy", + params, 1, getter_Copies(alertString)); + bundle->FormatStringFromName( + u"pop3ErrorDialogTitle", + params, 1, getter_Copies(dialogTitle)); + if (!alertString.IsEmpty()) + dialog->Alert(dialogTitle.get(), alertString.get()); +} + +NS_IMETHODIMP nsPop3Service::NewChannel(nsIURI *aURI, nsIChannel **_retval) +{ + return NewChannel2(aURI, nullptr, _retval); +} + +NS_IMETHODIMP nsPop3Service::NewChannel2(nsIURI *aURI, + nsILoadInfo *aLoadInfo, + nsIChannel **_retval) +{ + NS_ENSURE_ARG_POINTER(aURI); + nsresult rv; + + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(aURI, &rv); + nsCString realUserName; + if (NS_SUCCEEDED(rv) && url) + { + nsCOMPtr<nsIMsgIncomingServer> server; + url->GetServer(getter_AddRefs(server)); + if (server) + { + // find out if the server is busy or not...if the server is busy, we are + // *NOT* going to run the url. The error code isn't quite right... + // We might want to put up an error right here. + bool serverBusy = false; + rv = server->GetServerBusy(&serverBusy); + if (serverBusy) + { + AlertServerBusy(url); + return NS_MSG_FOLDER_BUSY; + } + server->GetRealUsername(realUserName); + } + } + + RefPtr<nsPop3Protocol> protocol = new nsPop3Protocol(aURI); + NS_ENSURE_TRUE(protocol, NS_ERROR_OUT_OF_MEMORY); + + rv = protocol->Initialize(aURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = protocol->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + protocol->SetUsername(realUserName.get()); + + return CallQueryInterface(protocol, _retval); +} + + +NS_IMETHODIMP +nsPop3Service::SetDefaultLocalPath(nsIFile *aPath) +{ + NS_ENSURE_ARG(aPath); + return NS_SetPersistentFile(PREF_MAIL_ROOT_POP3_REL, PREF_MAIL_ROOT_POP3, aPath); +} + +NS_IMETHODIMP +nsPop3Service::GetDefaultLocalPath(nsIFile **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + bool havePref; + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_POP3_REL, + PREF_MAIL_ROOT_POP3, + NS_APP_MAIL_50_DIR, + havePref, + getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = localFile->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775); + NS_ENSURE_SUCCESS(rv, rv); + + if (!havePref || !exists) { + rv = NS_SetPersistentFile(PREF_MAIL_ROOT_POP3_REL, PREF_MAIL_ROOT_POP3, localFile); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref."); + } + + NS_IF_ADDREF(*aResult = localFile); + return NS_OK; +} + + +NS_IMETHODIMP +nsPop3Service::GetServerIID(nsIID **aServerIID) +{ + *aServerIID = new nsIID(NS_GET_IID(nsIPop3IncomingServer)); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetRequiresUsername(bool *aRequiresUsername) +{ + NS_ENSURE_ARG_POINTER(aRequiresUsername); + *aRequiresUsername = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress) +{ + NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress); + *aPreflightPrettyNameWithEmailAddress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp) +{ + NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp); + *aCanLoginAtStartUp = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetCanDelete(bool *aCanDelete) +{ + NS_ENSURE_ARG_POINTER(aCanDelete); + *aCanDelete = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetCanDuplicate(bool *aCanDuplicate) +{ + NS_ENSURE_ARG_POINTER(aCanDuplicate); + *aCanDuplicate = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetCanGetMessages(bool *aCanGetMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetMessages); + *aCanGetMessages = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages); + *aCanGetIncomingMessages = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetShowComposeMsgLink(bool *showComposeMsgLink) +{ + NS_ENSURE_ARG_POINTER(showComposeMsgLink); + *showComposeMsgLink = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetFoldersCreatedAsync(bool *aAsyncCreation) +{ + NS_ENSURE_ARG_POINTER(aAsyncCreation); + *aAsyncCreation = false; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::GetDefaultServerPort(bool isSecure, int32_t *aPort) +{ + NS_ENSURE_ARG_POINTER(aPort); + + if (!isSecure) + return GetDefaultPort(aPort); + + *aPort = nsIPop3URL::DEFAULT_POP3S_PORT; + + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::NotifyDownloadStarted(nsIMsgFolder *aFolder) +{ + nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> >::ForwardIterator + iter(mListeners); + nsCOMPtr<nsIPop3ServiceListener> listener; + while (iter.HasMore()) { + listener = iter.GetNext(); + listener->OnDownloadStarted(aFolder); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::NotifyDownloadProgress(nsIMsgFolder *aFolder, + uint32_t aNumMessages, + uint32_t aNumTotalMessages) +{ + nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> >::ForwardIterator + iter(mListeners); + nsCOMPtr<nsIPop3ServiceListener> listener; + while (iter.HasMore()) { + listener = iter.GetNext(); + listener->OnDownloadProgress(aFolder, aNumMessages, aNumTotalMessages); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Service::NotifyDownloadCompleted(nsIMsgFolder *aFolder, + uint32_t aNumMessages) +{ + nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> >::ForwardIterator + iter(mListeners); + nsCOMPtr<nsIPop3ServiceListener> listener; + while (iter.HasMore()) { + listener = iter.GetNext(); + listener->OnDownloadCompleted(aFolder, aNumMessages); + } + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::AddListener(nsIPop3ServiceListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + mListeners.AppendElementUnlessExists(aListener); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Service::RemoveListener(nsIPop3ServiceListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + mListeners.RemoveElement(aListener); + return NS_OK; +} diff --git a/mailnews/local/src/nsPop3Service.h b/mailnews/local/src/nsPop3Service.h new file mode 100644 index 000000000..61dd9cf5c --- /dev/null +++ b/mailnews/local/src/nsPop3Service.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsPop3Service_h___ +#define nsPop3Service_h___ + +#include "nscore.h" + +#include "nsIPop3Service.h" +#include "nsIPop3URL.h" +#include "nsIUrlListener.h" +#include "nsIStreamListener.h" +#include "nsIProtocolHandler.h" +#include "nsIMsgProtocolInfo.h" +#include "nsTObserverArray.h" + +class nsIMsgMailNewsUrl; + +class nsPop3Service : public nsIPop3Service, + public nsIProtocolHandler, + public nsIMsgProtocolInfo +{ +public: + + nsPop3Service(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPOP3SERVICE + NS_DECL_NSIPROTOCOLHANDLER + NS_DECL_NSIMSGPROTOCOLINFO + +protected: + virtual ~nsPop3Service(); + nsresult GetMail(bool downloadNewMail, + nsIMsgWindow* aMsgWindow, + nsIUrlListener * aUrlListener, + nsIMsgFolder *inbox, + nsIPop3IncomingServer *popServer, + nsIURI ** aURL); + // convience function to make constructing of the pop3 url easier... + nsresult BuildPop3Url(const char * urlSpec, nsIMsgFolder *inbox, + nsIPop3IncomingServer *, nsIUrlListener * aUrlListener, + nsIURI ** aUrl, nsIMsgWindow *aMsgWindow); + + nsresult RunPopUrl(nsIMsgIncomingServer * aServer, nsIURI * aUrlToRun); + void AlertServerBusy(nsIMsgMailNewsUrl *url); + nsTObserverArray<nsCOMPtr<nsIPop3ServiceListener> > mListeners; +}; + +#endif /* nsPop3Service_h___ */ diff --git a/mailnews/local/src/nsPop3Sink.cpp b/mailnews/local/src/nsPop3Sink.cpp new file mode 100644 index 000000000..af68ea2be --- /dev/null +++ b/mailnews/local/src/nsPop3Sink.cpp @@ -0,0 +1,1026 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... +#include "nsPop3Sink.h" +#include "prprf.h" +#include "prlog.h" +#include "nscore.h" +#include <stdio.h> +#include <time.h> +#include "nsParseMailbox.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsLocalUtils.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsMailHeaders.h" +#include "nsIMsgAccountManager.h" +#include "nsILineInputStream.h" +#include "nsIPop3Protocol.h" +#include "nsLocalMailFolder.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIPrompt.h" +#include "nsIPromptService.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDocShell.h" +#include "mozIDOMWindow.h" +#include "nsEmbedCID.h" +#include "nsMsgUtils.h" +#include "nsMsgBaseCID.h" +#include "nsServiceManagerUtils.h" +#include "nsIPop3Service.h" +#include "nsMsgLocalCID.h" +#include "mozilla/Services.h" +#include "mozilla/Logging.h" + +/* for logging to Error Console */ +#include "nsIScriptError.h" +#include "nsIConsoleService.h" + +extern PRLogModuleInfo *POP3LOGMODULE; // defined in nsPop3Protocol.cpp +#define POP3LOG(str) "%s sink: [this=%p] " str, POP3LOGMODULE->name, this + +NS_IMPL_ISUPPORTS(nsPop3Sink, nsIPop3Sink) + +nsPop3Sink::nsPop3Sink() +{ + m_authed = false; + m_downloadingToTempFile = false; + m_biffState = 0; + m_numNewMessages = 0; + m_numNewMessagesInFolder = 0; + m_numMsgsDownloaded = 0; + m_senderAuthed = false; + m_outFileStream = nullptr; + m_uidlDownload = false; + m_buildMessageUri = false; + if (!POP3LOGMODULE) + POP3LOGMODULE = PR_NewLogModule("POP3"); +} + +nsPop3Sink::~nsPop3Sink() +{ + MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug, + (POP3LOG("Calling ReleaseFolderLock from ~nsPop3Sink"))); + ReleaseFolderLock(); +} + +nsresult +nsPop3Sink::SetUserAuthenticated(bool authed) +{ + m_authed = authed; + m_popServer->SetAuthenticated(authed); + return NS_OK; +} + +nsresult +nsPop3Sink::GetUserAuthenticated(bool* authed) +{ + return m_popServer->GetAuthenticated(authed); +} + +nsresult +nsPop3Sink::SetSenderAuthedFlag(void* closure, bool authed) +{ + m_authed = authed; + return NS_OK; +} + +nsresult +nsPop3Sink::SetMailAccountURL(const nsACString &urlString) +{ + m_accountUrl.Assign(urlString); + return NS_OK; +} + +nsresult +nsPop3Sink::GetMailAccountURL(nsACString &urlString) +{ + urlString.Assign(m_accountUrl); + return NS_OK; +} + +partialRecord::partialRecord() : + m_msgDBHdr(nullptr) +{ +} + +partialRecord::~partialRecord() +{ +} + +// Walk through all the messages in this folder and look for any +// PARTIAL messages. For each of those, dig thru the mailbox and +// find the Account that the message belongs to. If that Account +// matches the current Account, then look for the Uidl and save +// this message for later processing. +nsresult +nsPop3Sink::FindPartialMessages() +{ + nsCOMPtr<nsISimpleEnumerator> messages; + bool hasMore = false; + bool isOpen = false; + nsLocalFolderScanState folderScanState; + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder); + m_folder->GetMsgDatabase(getter_AddRefs(db)); + if (!localFolder || !db) + return NS_ERROR_FAILURE; // we need it to grub thru the folder + + nsresult rv = db->EnumerateMessages(getter_AddRefs(messages)); + if (messages) + messages->HasMoreElements(&hasMore); + while(hasMore && NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISupports> aSupport; + uint32_t flags = 0; + rv = messages->GetNext(getter_AddRefs(aSupport)); + nsCOMPtr<nsIMsgDBHdr> msgDBHdr(do_QueryInterface(aSupport, &rv)); + msgDBHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + { + // Open the various streams we need to seek and read from the mailbox + if (!isOpen) + { + rv = localFolder->GetFolderScanState(&folderScanState); + if (NS_SUCCEEDED(rv)) + isOpen = true; + else + break; + } + rv = localFolder->GetUidlFromFolder(&folderScanState, msgDBHdr); + if (!NS_SUCCEEDED(rv)) + break; + + // If we got the uidl, see if this partial message belongs to this + // account. Add it to the array if so... + if (folderScanState.m_uidl && + m_accountKey.Equals(folderScanState.m_accountKey, nsCaseInsensitiveCStringComparator())) + { + partialRecord *partialMsg = new partialRecord(); + if (partialMsg) + { + partialMsg->m_uidl = folderScanState.m_uidl; + partialMsg->m_msgDBHdr = msgDBHdr; + m_partialMsgsArray.AppendElement(partialMsg); + } + } + } + messages->HasMoreElements(&hasMore); + } + if (isOpen && folderScanState.m_inputStream) + folderScanState.m_inputStream->Close(); + return rv; +} + +// For all the partial messages saved by FindPartialMessages, +// ask the protocol handler if they still exist on the server. +// Any messages that don't exist any more are deleted from the +// msgDB. +void +nsPop3Sink::CheckPartialMessages(nsIPop3Protocol *protocol) +{ + uint32_t count = m_partialMsgsArray.Length(); + bool deleted = false; + + for (uint32_t i = 0; i < count; i++) + { + partialRecord *partialMsg; + bool found = true; + partialMsg = m_partialMsgsArray.ElementAt(i); + protocol->CheckMessage(partialMsg->m_uidl.get(), &found); + if (!found && partialMsg->m_msgDBHdr) + { + if (m_newMailParser) + m_newMailParser->m_mailDB->DeleteHeader(partialMsg->m_msgDBHdr, nullptr, false, true); + deleted = true; + } + delete partialMsg; + } + m_partialMsgsArray.Clear(); + if (deleted) + { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder); + if (localFolder) + localFolder->NotifyDelete(); + } +} + +nsresult +nsPop3Sink::BeginMailDelivery(bool uidlDownload, nsIMsgWindow *aMsgWindow, bool* aBool) +{ + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer); + if (!server) + return NS_ERROR_UNEXPECTED; + + m_window = aMsgWindow; + + nsCOMPtr <nsIMsgAccountManager> acctMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + nsCOMPtr <nsIMsgAccount> account; + NS_ENSURE_SUCCESS(rv, rv); + acctMgr->FindAccountForServer(server, getter_AddRefs(account)); + if (account) + account->GetKey(m_accountKey); + + bool isLocked; + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIPop3Sink*>(this)); + m_folder->GetLocked(&isLocked); + if(!isLocked) + { + MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug, + (POP3LOG("BeginMailDelivery acquiring semaphore"))); + m_folder->AcquireSemaphore(supports); + } + else + { + MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug, + (POP3LOG("BeginMailDelivery folder locked"))); + return NS_MSG_FOLDER_BUSY; + } + m_uidlDownload = uidlDownload; + if (!uidlDownload) + FindPartialMessages(); + + m_folder->GetNumNewMessages(false, &m_numNewMessagesInFolder); + +#ifdef DEBUG + printf("Begin mail message delivery.\n"); +#endif + nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pop3Service->NotifyDownloadStarted(m_folder); + if (aBool) + *aBool = true; + return NS_OK; +} + +nsresult +nsPop3Sink::EndMailDelivery(nsIPop3Protocol *protocol) +{ + CheckPartialMessages(protocol); + + if (m_newMailParser) + { + if (m_outFileStream) + m_outFileStream->Flush(); // try this. + m_newMailParser->OnStopRequest(nullptr, nullptr, NS_OK); + m_newMailParser->EndMsgDownload(); + } + if (m_outFileStream) + { + m_outFileStream->Close(); + m_outFileStream = nullptr; + } + + if (m_downloadingToTempFile) + m_tmpDownloadFile->Remove(false); + + // tell the parser to mark the db valid *after* closing the mailbox. + if (m_newMailParser) + m_newMailParser->UpdateDBFolderInfo(); + + MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug, + (POP3LOG("Calling ReleaseFolderLock from EndMailDelivery"))); + nsresult rv = ReleaseFolderLock(); + NS_ASSERTION(NS_SUCCEEDED(rv),"folder lock not released successfully"); + + bool filtersRun; + m_folder->CallFilterPlugins(nullptr, &filtersRun); // ??? do we need msgWindow? + int32_t numNewMessagesInFolder; + // if filters have marked msgs read or deleted, the num new messages count + // will go negative by the number of messages marked read or deleted, + // so if we add that number to the number of msgs downloaded, that will give + // us the number of actual new messages. + m_folder->GetNumNewMessages(false, &numNewMessagesInFolder); + m_numNewMessages -= (m_numNewMessagesInFolder - numNewMessagesInFolder); + m_folder->SetNumNewMessages(m_numNewMessages); // we'll adjust this for spam later + if (!filtersRun && m_numNewMessages > 0) + { + nsCOMPtr <nsIMsgIncomingServer> server; + m_folder->GetServer(getter_AddRefs(server)); + if (server) + { + server->SetPerformingBiff(true); + m_folder->SetBiffState(m_biffState); + server->SetPerformingBiff(false); + } + } + // note that size on disk has possibly changed. + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder); + if (localFolder) + (void) localFolder->RefreshSizeOnDisk(); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer); + if (server) + { + nsCOMPtr <nsIMsgFilterList> filterList; + rv = server->GetFilterList(nullptr, getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv, rv); + + if (filterList) + (void) filterList->FlushLogIfNecessary(); + } + + // fix for bug #161999 + // we should update the summary totals for the folder (inbox) + // in case it's not the open folder + m_folder->UpdateSummaryTotals(true); + + // check if the folder open in this window is not the current folder, and if it has new + // message, in which case we need to try to run the filter plugin. + if (m_newMailParser) + { + nsCOMPtr <nsIMsgWindow> msgWindow; + m_newMailParser->GetMsgWindow(getter_AddRefs(msgWindow)); + // this breaks down if it's biff downloading new mail because + // there's no msgWindow... + if (msgWindow) + { + nsCOMPtr <nsIMsgFolder> openFolder; + (void) msgWindow->GetOpenFolder(getter_AddRefs(openFolder)); + if (openFolder && openFolder != m_folder) + { + // only call filter plugins if folder is a local folder, because only + // local folders get messages filtered into them synchronously by pop3. + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(openFolder); + if (localFolder) + { + bool hasNew, isLocked; + (void) openFolder->GetHasNewMessages(&hasNew); + if (hasNew) + { + // if the open folder is locked, we shouldn't run the spam filters + // on it because someone is using the folder. see 218433. + // Ideally, the filter plugin code would try to grab the folder lock + // and hold onto it until done, but that's more difficult and I think + // this will actually fix the problem. + openFolder->GetLocked(&isLocked); + if(!isLocked) + openFolder->CallFilterPlugins(nullptr, &filtersRun); + } + } + } + } + } +#ifdef DEBUG + printf("End mail message delivery.\n"); +#endif + nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pop3Service->NotifyDownloadCompleted(m_folder, m_numNewMessages); + return NS_OK; +} + +nsresult +nsPop3Sink::ReleaseFolderLock() +{ + nsresult result = NS_OK; + if (!m_folder) + return result; + bool haveSemaphore; + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIPop3Sink*>(this)); + result = m_folder->TestSemaphore(supports, &haveSemaphore); + MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug, + (POP3LOG("ReleaseFolderLock haveSemaphore = %s"), haveSemaphore ? "TRUE" : "FALSE")); + + if(NS_SUCCEEDED(result) && haveSemaphore) + result = m_folder->ReleaseSemaphore(supports); + return result; +} + +nsresult +nsPop3Sink::AbortMailDelivery(nsIPop3Protocol *protocol) +{ + CheckPartialMessages(protocol); + + // ### PS TODO - discard any new message? + + if (m_outFileStream) + { + m_outFileStream->Close(); + m_outFileStream = nullptr; + } + + if (m_downloadingToTempFile && m_tmpDownloadFile) + m_tmpDownloadFile->Remove(false); + + /* tell the parser to mark the db valid *after* closing the mailbox. + we have truncated the inbox, so berkeley mailbox and msf file are in sync*/ + if (m_newMailParser) + m_newMailParser->UpdateDBFolderInfo(); + MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug, + (POP3LOG("Calling ReleaseFolderLock from AbortMailDelivery"))); + + nsresult rv = ReleaseFolderLock(); + NS_ASSERTION(NS_SUCCEEDED(rv),"folder lock not released successfully"); + +#ifdef DEBUG + printf("Abort mail message delivery.\n"); +#endif + nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pop3Service->NotifyDownloadCompleted(m_folder, 0); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::IncorporateBegin(const char* uidlString, + nsIURI* aURL, + uint32_t flags, + void** closure) +{ +#ifdef DEBUG + printf("Incorporate message begin:\n"); + if (uidlString) + printf("uidl string: %s\n", uidlString); +#endif + nsCOMPtr<nsIFile> path; + + m_folder->GetFilePath(getter_AddRefs(path)); + + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + nsCOMPtr<nsIMsgIncomingServer> server; + m_folder->GetServer(getter_AddRefs(server)); + nsCString plugStoreContract; + server->GetCharValue("storeContractID", plugStoreContract); + // Maildir doesn't care about quaranting, but other stores besides berkeley + // mailbox might. We should probably make this an attribute on the pluggable + // store, though. + if (plugStoreContract.Equals( + NS_LITERAL_CSTRING("@mozilla.org/msgstore/berkeleystore;1"))) + pPrefBranch->GetBoolPref("mailnews.downloadToTempFile", &m_downloadingToTempFile); + } + + nsCOMPtr<nsIMsgDBHdr> newHdr; + + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer); + if (!server) + return NS_ERROR_UNEXPECTED; + + if (m_downloadingToTempFile) + { + // need to create an nsIOFileStream from a temp file... + nsCOMPtr<nsIFile> tmpDownloadFile; + rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "newmsg", + getter_AddRefs(tmpDownloadFile)); + + NS_ASSERTION(NS_SUCCEEDED(rv), + "writing tmp pop3 download file: failed to append filename"); + if (NS_FAILED(rv)) + return rv; + + if (!m_tmpDownloadFile) + { + //need a unique tmp file to prevent dataloss in multiuser environment + rv = tmpDownloadFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + NS_ENSURE_SUCCESS(rv, rv); + + m_tmpDownloadFile = do_QueryInterface(tmpDownloadFile, &rv); + } + if (NS_SUCCEEDED(rv)) + { + rv = MsgGetFileStream(m_tmpDownloadFile, getter_AddRefs(m_outFileStream)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + else + { + rv = server->GetMsgStore(getter_AddRefs(m_msgStore)); + bool reusable; + NS_ENSURE_SUCCESS(rv, rv); + m_msgStore->GetNewMsgOutputStream(m_folder, getter_AddRefs(newHdr), + &reusable, getter_AddRefs(m_outFileStream)); + } + // The following (!m_outFileStream etc) was added to make sure that we don't + // write somewhere where for some reason or another we can't write to and + // lose the messages. See bug 62480 + if (!m_outFileStream) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream); + + // create a new mail parser + if (!m_newMailParser) + m_newMailParser = new nsParseNewMailState; + NS_ENSURE_TRUE(m_newMailParser, NS_ERROR_OUT_OF_MEMORY); + if (m_uidlDownload) + m_newMailParser->DisableFilters(); + + nsCOMPtr <nsIMsgFolder> serverFolder; + rv = GetServerFolder(getter_AddRefs(serverFolder)); + if (NS_FAILED(rv)) return rv; + + rv = m_newMailParser->Init(serverFolder, m_folder, + m_window, newHdr, m_outFileStream); + // If we failed to initialize the parser, then just don't use it!!! + // We can still continue without one. + + if (NS_FAILED(rv)) + { + m_newMailParser = nullptr; + rv = NS_OK; + } + + if (closure) + *closure = (void*) this; + +#ifdef DEBUG + // Debugging, see bug 1116055. + int64_t first_pre_seek_pos; + nsresult rv3 = seekableOutStream->Tell(&first_pre_seek_pos); +#endif + + // XXX Handle error such as network error for remote file system. + seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + +#ifdef DEBUG + // Debugging, see bug 1116055. + int64_t first_post_seek_pos; + nsresult rv4 = seekableOutStream->Tell(&first_post_seek_pos); + if (NS_SUCCEEDED(rv3) && NS_SUCCEEDED(rv4)) { + if (first_pre_seek_pos != first_post_seek_pos) { + nsCOMPtr<nsIMsgFolder> localFolder = do_QueryInterface(m_folder); + nsString folderName; + if (localFolder) + localFolder->GetPrettiestName(folderName); + if (!folderName.IsEmpty()) { + fprintf(stderr,"(seekdebug) Seek was necessary in IncorporateBegin() for folder %s.\n", + NS_ConvertUTF16toUTF8(folderName).get()); + } else { + fprintf(stderr,"(seekdebug) Seek was necessary in IncorporateBegin().\n"); + } + fprintf(stderr,"(seekdebug) first_pre_seek_pos = 0x%016llx, first_post_seek_pos=0x%016llx\n", + (unsigned long long) first_pre_seek_pos, (unsigned long long) first_post_seek_pos); + } + } +#endif + + nsCString outputString(GetDummyEnvelope()); + rv = WriteLineToMailbox(outputString); + NS_ENSURE_SUCCESS(rv, rv); + // Write out account-key before UIDL so the code that looks for + // UIDL will find the account first and know it can stop looking + // once it finds the UIDL line. + if (!m_accountKey.IsEmpty()) + { + outputString.AssignLiteral(HEADER_X_MOZILLA_ACCOUNT_KEY ": "); + outputString.Append(m_accountKey); + outputString.AppendLiteral(MSG_LINEBREAK); + rv = WriteLineToMailbox(outputString); + NS_ENSURE_SUCCESS(rv, rv); + } + if (uidlString) + { + outputString.AssignLiteral("X-UIDL: "); + outputString.Append(uidlString); + outputString.AppendLiteral(MSG_LINEBREAK); + rv = WriteLineToMailbox(outputString); + NS_ENSURE_SUCCESS(rv, rv); + } + + // WriteLineToMailbox("X-Mozilla-Status: 8000" MSG_LINEBREAK); + char *statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flags); + outputString.Assign(statusLine); + rv = WriteLineToMailbox(outputString); + PR_smprintf_free(statusLine); + NS_ENSURE_SUCCESS(rv, rv); + + rv = WriteLineToMailbox(NS_LITERAL_CSTRING("X-Mozilla-Status2: 00000000" MSG_LINEBREAK)); + NS_ENSURE_SUCCESS(rv, rv); + + // leave space for 60 bytes worth of keys/tags + rv = WriteLineToMailbox(NS_LITERAL_CSTRING(X_MOZILLA_KEYWORDS)); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::SetPopServer(nsIPop3IncomingServer *server) +{ + m_popServer = server; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::GetPopServer(nsIPop3IncomingServer **aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + NS_IF_ADDREF(*aServer = m_popServer); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Sink::GetFolder(nsIMsgFolder **aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_IF_ADDREF(*aFolder = m_folder); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Sink::SetFolder(nsIMsgFolder * aFolder) +{ + m_folder = aFolder; + return NS_OK; +} + +nsresult +nsPop3Sink::GetServerFolder(nsIMsgFolder **aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + + if (m_popServer) + { + // not sure what this is used for - might be wrong if we have a deferred account. + nsCOMPtr <nsIMsgIncomingServer> incomingServer = do_QueryInterface(m_popServer); + if (incomingServer) + return incomingServer->GetRootFolder(aFolder); + } + *aFolder = nullptr; + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsPop3Sink::SetMsgsToDownload(uint32_t aNumMessages) +{ + m_numNewMessages = aNumMessages; + return NS_OK; +} + +char* +nsPop3Sink::GetDummyEnvelope(void) +{ + static char result[75]; + char *ct; + time_t now = time ((time_t *) 0); +#if defined (XP_WIN) + if (now < 0 || now > 0x7FFFFFFF) + now = 0x7FFFFFFF; +#endif + ct = ctime(&now); + PR_ASSERT(ct[24] == '\r' || ct[24] == '\n'); + ct[24] = 0; + /* This value must be in ctime() format, with English abbreviations. + strftime("... %c ...") is no good, because it is localized. */ + PL_strcpy(result, "From - "); + PL_strcpy(result + 7, ct); + PL_strcpy(result + 7 + 24, MSG_LINEBREAK); + return result; +} + +nsresult +nsPop3Sink::IncorporateWrite(const char* block, + int32_t length) +{ + m_outputBuffer.Truncate(); + if (!strncmp(block, "From ", 5)) + m_outputBuffer.Assign('>'); + + m_outputBuffer.Append(block); + + return WriteLineToMailbox(m_outputBuffer); +} + +nsresult nsPop3Sink::WriteLineToMailbox(const nsACString& buffer) +{ + if (!buffer.IsEmpty()) + { + uint32_t bufferLen = buffer.Length(); + if (m_newMailParser) + m_newMailParser->HandleLine(buffer.BeginReading(), bufferLen); + // The following (!m_outFileStream etc) was added to make sure that we don't write somewhere + // where for some reason or another we can't write to and lose the messages + // See bug 62480 + NS_ENSURE_TRUE(m_outFileStream, NS_ERROR_OUT_OF_MEMORY); + + // To remove seeking to the end for each line to be written, remove the + // following line. See bug 1116055 for details. +#define SEEK_TO_END +#ifdef SEEK_TO_END + // seek to the end in case someone else has seeked elsewhere in our stream. + nsCOMPtr <nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream); + + int64_t before_seek_pos; + nsresult rv2 = seekableOutStream->Tell(&before_seek_pos); + MOZ_ASSERT(NS_SUCCEEDED(rv2), "seekableOutStream->Tell(&before_seek_pos) failed"); + + // XXX Handle error such as network error for remote file system. + seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + + int64_t after_seek_pos; + nsresult rv3 = seekableOutStream->Tell(&after_seek_pos); + MOZ_ASSERT(NS_SUCCEEDED(rv3), "seekableOutStream->Tell(&after_seek_pos) failed"); + + if (NS_SUCCEEDED(rv2) && NS_SUCCEEDED(rv3)) { + if (before_seek_pos != after_seek_pos) { + nsCOMPtr<nsIMsgFolder> localFolder = do_QueryInterface(m_folder); + nsString folderName; + if (localFolder) + localFolder->GetPrettiestName(folderName); + // This merits a console message, it's poor man's telemetry. + MsgLogToConsole4( + NS_LITERAL_STRING("Unexpected file position change detected") + + (folderName.IsEmpty() ? EmptyString() : NS_LITERAL_STRING(" in folder ")) + + (folderName.IsEmpty() ? EmptyString() : folderName) + NS_LITERAL_STRING(". " + "If you can reliably reproduce this, please report the steps " + "you used to dev-apps-thunderbird@lists.mozilla.org or to bug 1308335 at bugzilla.mozilla.org. " + "Resolving this problem will allow speeding up message downloads."), + NS_LITERAL_STRING(__FILE__), __LINE__, nsIScriptError::errorFlag); +#ifdef DEBUG + // Debugging, see bug 1116055. + if (!folderName.IsEmpty()) { + fprintf(stderr,"(seekdebug) WriteLineToMailbox() detected an unexpected file position change in folder %s.\n", + NS_ConvertUTF16toUTF8(folderName).get()); + } else { + fprintf(stderr,"(seekdebug) WriteLineToMailbox() detected an unexpected file position change.\n"); + } + fprintf(stderr,"(seekdebug) before_seek_pos=0x%016llx, after_seek_pos=0x%016llx\n", + (long long unsigned int) before_seek_pos, (long long unsigned int) after_seek_pos); +#endif + } + } +#endif + + uint32_t bytesWritten; + m_outFileStream->Write(buffer.BeginReading(), bufferLen, &bytesWritten); + NS_ENSURE_TRUE(bytesWritten == bufferLen, NS_ERROR_FAILURE); + } + return NS_OK; +} + +nsresult nsPop3Sink::HandleTempDownloadFailed(nsIMsgWindow *msgWindow) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + nsString fromStr, subjectStr, confirmString; + + m_newMailParser->m_newMsgHdr->GetMime2DecodedSubject(subjectStr); + m_newMailParser->m_newMsgHdr->GetMime2DecodedAuthor(fromStr); + const char16_t *params[] = { fromStr.get(), subjectStr.get() }; + bundle->FormatStringFromName( + u"pop3TmpDownloadError", + params, 2, getter_Copies(confirmString)); + nsCOMPtr<mozIDOMWindowProxy> parentWindow; + nsCOMPtr<nsIPromptService> promptService = do_GetService(NS_PROMPTSERVICE_CONTRACTID); + nsCOMPtr<nsIDocShell> docShell; + if (msgWindow) + { + (void) msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + parentWindow = do_QueryInterface(docShell); + } + if (promptService && !confirmString.IsEmpty()) + { + int32_t dlgResult = -1; + bool dummyValue = false; + rv = promptService->ConfirmEx(parentWindow, nullptr, confirmString.get(), + nsIPromptService::STD_YES_NO_BUTTONS, + nullptr, + nullptr, + nullptr, + nullptr, + &dummyValue, + &dlgResult); + m_newMailParser->m_newMsgHdr = nullptr; + + return (dlgResult == 0) ? NS_OK : NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD; + } + return rv; +} + + +NS_IMETHODIMP +nsPop3Sink::IncorporateComplete(nsIMsgWindow *aMsgWindow, int32_t aSize) +{ + if (m_buildMessageUri && !m_baseMessageUri.IsEmpty() && m_newMailParser && + m_newMailParser->m_newMsgHdr) + { + nsMsgKey msgKey; + m_newMailParser->m_newMsgHdr->GetMessageKey(&msgKey); + m_messageUri.Truncate(); + nsBuildLocalMessageURI(m_baseMessageUri.get(), msgKey, m_messageUri); + } + + nsresult rv = WriteLineToMailbox(NS_LITERAL_CSTRING(MSG_LINEBREAK)); + NS_ENSURE_SUCCESS(rv, rv); + bool leaveOnServer = false; + m_popServer->GetLeaveMessagesOnServer(&leaveOnServer); + // We need to flush the output stream, in case mail filters move + // the new message, which relies on all the data being flushed. + rv = m_outFileStream->Flush(); // Make sure the message is written to the disk + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(m_newMailParser, "could not get m_newMailParser"); + if (m_newMailParser) + { + // PublishMsgHdr clears m_newMsgHdr, so we need a comptr to + // hold onto it. + nsCOMPtr<nsIMsgDBHdr> hdr = m_newMailParser->m_newMsgHdr; + NS_ASSERTION(hdr, "m_newMailParser->m_newMsgHdr wasn't set"); + if (!hdr) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder); + bool doSelect = false; + + // aSize is only set for partial messages. For full messages, + // check to see if we're replacing an old partial message. + if (!aSize && localFolder) + (void) localFolder->DeleteDownloadMsg(hdr, &doSelect); + + // If a header already exists for this message (for example, when + // getting a complete message when a partial exists), then update the new + // header from the old. + if (!m_origMessageUri.IsEmpty() && localFolder) + { + nsCOMPtr <nsIMsgDBHdr> oldMsgHdr; + rv = GetMsgDBHdrFromURI(m_origMessageUri.get(), getter_AddRefs(oldMsgHdr)); + if (NS_SUCCEEDED(rv) && oldMsgHdr) + localFolder->UpdateNewMsgHdr(oldMsgHdr, hdr); + } + + if (m_downloadingToTempFile) + { + // close file to give virus checkers a chance to do their thing... + m_outFileStream->Flush(); + m_outFileStream->Close(); + m_newMailParser->FinishHeader(); + // need to re-open the inbox file stream. + bool exists; + m_tmpDownloadFile->Exists(&exists); + if (!exists) + return HandleTempDownloadFailed(aMsgWindow); + + nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream); + rv = MsgReopenFileStream(m_tmpDownloadFile, inboxInputStream); + NS_ENSURE_SUCCESS(rv, HandleTempDownloadFailed(aMsgWindow)); + if (m_outFileStream) + { + int64_t tmpDownloadFileSize; + uint32_t msgSize; + hdr->GetMessageSize(&msgSize); + // we need to clone because nsLocalFileUnix caches its stat result, + // so it doesn't realize the file has changed size. + nsCOMPtr <nsIFile> tmpClone; + rv = m_tmpDownloadFile->Clone(getter_AddRefs(tmpClone)); + NS_ENSURE_SUCCESS(rv, rv); + tmpClone->GetFileSize(&tmpDownloadFileSize); + + if (msgSize > tmpDownloadFileSize) + rv = NS_MSG_ERROR_WRITING_MAIL_FOLDER; + else + rv = m_newMailParser->AppendMsgFromStream(inboxInputStream, hdr, + msgSize, m_folder); + if (NS_FAILED(rv)) + return HandleTempDownloadFailed(aMsgWindow); + + m_outFileStream->Close(); // close so we can truncate. + m_tmpDownloadFile->SetFileSize(0); + } + else + { + return HandleTempDownloadFailed(aMsgWindow); + // need to give an error here. + } + } + else + { + m_msgStore->FinishNewMessage(m_outFileStream, hdr); + } + m_newMailParser->PublishMsgHeader(aMsgWindow); + // run any reply/forward filter after we've finished with the + // temp quarantine file, and/or moved the message to another folder. + m_newMailParser->ApplyForwardAndReplyFilter(aMsgWindow); + if (aSize) + hdr->SetUint32Property("onlineSize", aSize); + + // if DeleteDownloadMsg requested it, select the new message + else if (doSelect) + (void) localFolder->SelectDownloadMsg(); + } + +#ifdef DEBUG + printf("Incorporate message complete.\n"); +#endif + nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pop3Service->NotifyDownloadProgress(m_folder, ++m_numMsgsDownloaded, m_numNewMessages); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::IncorporateAbort(bool uidlDownload) +{ + nsresult rv = m_outFileStream->Close(); + NS_ENSURE_SUCCESS(rv,rv); + if (!m_downloadingToTempFile && m_msgStore && m_newMailParser && + m_newMailParser->m_newMsgHdr) + { + m_msgStore->DiscardNewMessage(m_outFileStream, + m_newMailParser->m_newMsgHdr); + } +#ifdef DEBUG + printf("Incorporate message abort.\n"); +#endif + return rv; +} + +nsresult +nsPop3Sink::BiffGetNewMail() +{ +#ifdef DEBUG + printf("Biff get new mail.\n"); +#endif + return NS_OK; +} + +nsresult +nsPop3Sink::SetBiffStateAndUpdateFE(uint32_t aBiffState, int32_t numNewMessages, bool notify) +{ + m_biffState = aBiffState; + if (m_newMailParser) + numNewMessages -= m_newMailParser->m_numNotNewMessages; + + if (notify && m_folder && numNewMessages > 0 && numNewMessages != m_numNewMessages + && aBiffState == nsIMsgFolder::nsMsgBiffState_NewMail) + { + m_folder->SetNumNewMessages(numNewMessages); + m_folder->SetBiffState(aBiffState); + } + m_numNewMessages = numNewMessages; + + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::GetBuildMessageUri(bool *bVal) +{ + NS_ENSURE_ARG_POINTER(bVal); + *bVal = m_buildMessageUri; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::SetBuildMessageUri(bool bVal) +{ + m_buildMessageUri = bVal; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::GetMessageUri(char **messageUri) +{ + NS_ENSURE_ARG_POINTER(messageUri); + NS_ENSURE_TRUE(!m_messageUri.IsEmpty(), NS_ERROR_FAILURE); + *messageUri = ToNewCString(m_messageUri); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::SetMessageUri(const char *messageUri) +{ + NS_ENSURE_ARG_POINTER(messageUri); + m_messageUri = messageUri; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::GetBaseMessageUri(char ** baseMessageUri) +{ + NS_ENSURE_ARG_POINTER(baseMessageUri); + NS_ENSURE_TRUE(!m_baseMessageUri.IsEmpty(), NS_ERROR_FAILURE); + *baseMessageUri = ToNewCString(m_baseMessageUri); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::SetBaseMessageUri(const char *baseMessageUri) +{ + NS_ENSURE_ARG_POINTER(baseMessageUri); + m_baseMessageUri = baseMessageUri; + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::GetOrigMessageUri(nsACString& aOrigMessageUri) +{ + aOrigMessageUri.Assign(m_origMessageUri); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3Sink::SetOrigMessageUri(const nsACString& aOrigMessageUri) +{ + m_origMessageUri.Assign(aOrigMessageUri); + return NS_OK; +} diff --git a/mailnews/local/src/nsPop3Sink.h b/mailnews/local/src/nsPop3Sink.h new file mode 100644 index 000000000..bd11eba35 --- /dev/null +++ b/mailnews/local/src/nsPop3Sink.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ +#ifndef nsPop3Sink_h__ +#define nsPop3Sink_h__ + +#include "nscore.h" +#include "nsIURL.h" +#include "nsIPop3Sink.h" +#include "nsIOutputStream.h" +#include "prmem.h" +#include "prio.h" +#include "plstr.h" +#include "prenv.h" +#include "nsIMsgFolder.h" +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "nsStringGlue.h" + +class nsParseNewMailState; +class nsIMsgFolder; + +struct partialRecord +{ + partialRecord(); + ~partialRecord(); + + nsCOMPtr<nsIMsgDBHdr> m_msgDBHdr; + nsCString m_uidl; +}; + +class nsPop3Sink : public nsIPop3Sink +{ +public: + nsPop3Sink(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPOP3SINK + nsresult GetServerFolder(nsIMsgFolder **aFolder); + nsresult FindPartialMessages(); + void CheckPartialMessages(nsIPop3Protocol *protocol); + + static char* GetDummyEnvelope(void); + +protected: + virtual ~nsPop3Sink(); + nsresult WriteLineToMailbox(const nsACString& buffer); + nsresult ReleaseFolderLock(); + nsresult HandleTempDownloadFailed(nsIMsgWindow *msgWindow); + + bool m_authed; + nsCString m_accountUrl; + uint32_t m_biffState; + int32_t m_numNewMessages; + int32_t m_numNewMessagesInFolder; + int32_t m_numMsgsDownloaded; + bool m_senderAuthed; + nsCString m_outputBuffer; + nsCOMPtr<nsIPop3IncomingServer> m_popServer; + //Currently the folder we want to update about biff info + nsCOMPtr<nsIMsgFolder> m_folder; + RefPtr<nsParseNewMailState> m_newMailParser; + nsCOMPtr <nsIOutputStream> m_outFileStream; // the file we write to, which may be temporary + nsCOMPtr<nsIMsgPluggableStore> m_msgStore; + bool m_uidlDownload; + bool m_buildMessageUri; + bool m_downloadingToTempFile; + nsCOMPtr <nsIFile> m_tmpDownloadFile; + nsCOMPtr<nsIMsgWindow> m_window; + nsCString m_messageUri; + nsCString m_baseMessageUri; + nsCString m_origMessageUri; + nsCString m_accountKey; + nsTArray<partialRecord*> m_partialMsgsArray; +}; + +#endif diff --git a/mailnews/local/src/nsPop3URL.cpp b/mailnews/local/src/nsPop3URL.cpp new file mode 100644 index 000000000..f3d724793 --- /dev/null +++ b/mailnews/local/src/nsPop3URL.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... + +#include "nsIURL.h" +#include "nsPop3URL.h" +#include "nsPop3Protocol.h" +#include "nsStringGlue.h" +#include "prmem.h" +#include "plstr.h" +#include "prprf.h" + +nsPop3URL::nsPop3URL(): nsMsgMailNewsUrl() +{ +} + +nsPop3URL::~nsPop3URL() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsPop3URL, nsMsgMailNewsUrl, nsIPop3URL) + + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsIPop3URL specific support +//////////////////////////////////////////////////////////////////////////////////// + +nsresult nsPop3URL::SetPop3Sink(nsIPop3Sink* aPop3Sink) +{ + if (aPop3Sink) + m_pop3Sink = aPop3Sink; + return NS_OK; +} + +nsresult nsPop3URL::GetPop3Sink(nsIPop3Sink** aPop3Sink) +{ + if (aPop3Sink) + { + *aPop3Sink = m_pop3Sink; + NS_IF_ADDREF(*aPop3Sink); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPop3URL::GetMessageUri(char ** aMessageUri) +{ + if(!aMessageUri || m_messageUri.IsEmpty()) + return NS_ERROR_NULL_POINTER; + *aMessageUri = ToNewCString(m_messageUri); + return NS_OK; +} + +NS_IMETHODIMP +nsPop3URL::SetMessageUri(const char *aMessageUri) +{ + if (aMessageUri) + m_messageUri = aMessageUri; + return NS_OK; +} diff --git a/mailnews/local/src/nsPop3URL.h b/mailnews/local/src/nsPop3URL.h new file mode 100644 index 000000000..4c6ec6b0a --- /dev/null +++ b/mailnews/local/src/nsPop3URL.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsPop3URL_h__ +#define nsPop3URL_h__ + +#include "nsIPop3URL.h" +#include "nsMsgMailNewsUrl.h" +#include "nsIMsgIncomingServer.h" +#include "nsCOMPtr.h" + +class nsPop3URL : public nsIPop3URL, public nsMsgMailNewsUrl +{ +public: + NS_DECL_NSIPOP3URL + nsPop3URL(); + NS_DECL_ISUPPORTS_INHERITED + +protected: + virtual ~nsPop3URL(); + + nsCString m_messageUri; + + /* Pop3 specific event sinks */ + nsCOMPtr<nsIPop3Sink> m_pop3Sink; +}; + +#endif // nsPop3URL_h__ diff --git a/mailnews/local/src/nsRssIncomingServer.cpp b/mailnews/local/src/nsRssIncomingServer.cpp new file mode 100644 index 000000000..fe1e8abe3 --- /dev/null +++ b/mailnews/local/src/nsRssIncomingServer.cpp @@ -0,0 +1,260 @@ +/* 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 "nsIRssService.h" +#include "nsRssIncomingServer.h" +#include "nsMsgFolderFlags.h" +#include "nsINewsBlogFeedDownloader.h" +#include "nsMsgBaseCID.h" +#include "nsIFile.h" +#include "nsIMsgFolderNotificationService.h" + +#include "nsIMsgLocalMailFolder.h" +#include "nsIDBFolderInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsArrayUtils.h" +#include "nsMsgUtils.h" + +nsrefcnt nsRssIncomingServer::gInstanceCount = 0; + +NS_IMPL_ISUPPORTS_INHERITED(nsRssIncomingServer, + nsMsgIncomingServer, + nsIRssIncomingServer, + nsIMsgFolderListener, + nsILocalMailIncomingServer) + +nsRssIncomingServer::nsRssIncomingServer() +{ + m_canHaveFilters = true; + + if (gInstanceCount == 0) + { + nsresult rv; + nsCOMPtr<nsIMsgFolderNotificationService> notifyService = + do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + notifyService->AddListener(this, + nsIMsgFolderNotificationService::folderAdded | + nsIMsgFolderNotificationService::folderDeleted | + nsIMsgFolderNotificationService::folderMoveCopyCompleted | + nsIMsgFolderNotificationService::folderRenamed); + } + + gInstanceCount++; +} + +nsRssIncomingServer::~nsRssIncomingServer() +{ + gInstanceCount--; + + if (gInstanceCount == 0) + { + nsresult rv; + nsCOMPtr<nsIMsgFolderNotificationService> notifyService = + do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + notifyService->RemoveListener(this); + } +} + +nsresult nsRssIncomingServer::FillInDataSourcePath(const nsAString& aDataSourceName, + nsIFile ** aLocation) +{ + nsresult rv; + // Get the local path for this server. + nsCOMPtr<nsIFile> localFile; + rv = GetLocalPath(getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // Append the name of the subscriptions data source. + rv = localFile->Append(aDataSourceName); + NS_IF_ADDREF(*aLocation = localFile); + return rv; +} + +// nsIRSSIncomingServer methods +NS_IMETHODIMP nsRssIncomingServer::GetSubscriptionsDataSourcePath(nsIFile ** aLocation) +{ + return FillInDataSourcePath(NS_LITERAL_STRING("feeds.rdf"), aLocation); +} + +NS_IMETHODIMP nsRssIncomingServer::GetFeedItemsDataSourcePath(nsIFile ** aLocation) +{ + return FillInDataSourcePath(NS_LITERAL_STRING("feeditems.rdf"), aLocation); +} + +NS_IMETHODIMP nsRssIncomingServer::CreateDefaultMailboxes() +{ + // For Feeds, all we have is Trash. + return CreateLocalFolder(NS_LITERAL_STRING("Trash")); +} + +NS_IMETHODIMP nsRssIncomingServer::SetFlagsOnDefaultMailboxes() +{ + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(rootFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::Trash); + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow) +{ + // Get the account root (server) folder and pass it on. + nsCOMPtr<nsIMsgFolder> rootRSSFolder; + GetRootMsgFolder(getter_AddRefs(rootRSSFolder)); + nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(rootRSSFolder); + nsresult rv; + bool isBiff = true; + nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader = + do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rssDownloader->DownloadFeed(rootRSSFolder, urlListener, isBiff, aMsgWindow); + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener, + nsIMsgFolder *aFolder, + nsIURI **_retval) +{ + // Pass the selected folder on to the downloader. + NS_ENSURE_ARG_POINTER(aFolder); + nsresult rv; + bool isBiff = false; + nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader = + do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rssDownloader->DownloadFeed(aFolder, aUrlListener, isBiff, aMsgWindow); + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::GetAccountManagerChrome(nsAString& aResult) +{ + aResult.AssignLiteral("am-newsblog.xul"); + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel) +{ + NS_ENSURE_ARG_POINTER(aSupportLevel); + *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE; + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::GetSupportsDiskSpace(bool *aSupportsDiskSpace) +{ + NS_ENSURE_ARG_POINTER(aSupportsDiskSpace); + *aSupportsDiskSpace = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) +{ + NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff); + // For Feed folders, we don't require a password. + *aServerRequiresPasswordForBiff = false; + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::GetCanSearchMessages(bool *canSearchMessages) +{ + NS_ENSURE_ARG_POINTER(canSearchMessages); + *canSearchMessages = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgAdded(nsIMsgDBHdr *aMsg) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgsClassified(nsIArray *aMsgs, + bool aJunkProcessed, + bool aTraitProcessed) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgsDeleted(nsIArray *aMsgs) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgsMoveCopyCompleted(bool aMove, + nsIArray *aSrcMsgs, + nsIMsgFolder *aDestFolder, + nsIArray *aDestMsgs) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgKeyChanged(nsMsgKey aOldKey, + nsIMsgDBHdr *aNewHdr) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::FolderAdded(nsIMsgFolder *aFolder) +{ + // Nothing to do. Not necessary for new folder adds, as a new folder never + // has a subscription. + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::FolderDeleted(nsIMsgFolder *aFolder) +{ + // Not necessary for folder deletes, which are move to Trash and handled by + // movecopy. Virtual folder or trash folder deletes send a folderdeleted, + // but these should have no subscriptions already. + return NS_OK; +} + +NS_IMETHODIMP nsRssIncomingServer::FolderMoveCopyCompleted(bool aMove, + nsIMsgFolder *aSrcFolder, + nsIMsgFolder *aDestFolder) +{ + return FolderChanged(aDestFolder, aSrcFolder, (aMove ? "move" : "copy")); +} + +NS_IMETHODIMP nsRssIncomingServer::FolderRenamed(nsIMsgFolder *aOrigFolder, + nsIMsgFolder *aNewFolder) +{ + return FolderChanged(aNewFolder, aOrigFolder, "rename"); +} + +NS_IMETHODIMP nsRssIncomingServer::ItemEvent(nsISupports *aItem, + const nsACString &aEvent, + nsISupports *aData) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsRssIncomingServer::FolderChanged(nsIMsgFolder *aFolder, + nsIMsgFolder *aOrigFolder, + const char *aAction) +{ + if (!aFolder) + return NS_OK; + + nsresult rv; + nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader = + do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rssDownloader->UpdateSubscriptionsDS(aFolder, aOrigFolder, aAction); + return rv; +} + +NS_IMETHODIMP +nsRssIncomingServer::GetSortOrder(int32_t* aSortOrder) +{ + NS_ENSURE_ARG_POINTER(aSortOrder); + *aSortOrder = 400000000; + return NS_OK; +} diff --git a/mailnews/local/src/nsRssIncomingServer.h b/mailnews/local/src/nsRssIncomingServer.h new file mode 100644 index 000000000..67406f19a --- /dev/null +++ b/mailnews/local/src/nsRssIncomingServer.h @@ -0,0 +1,43 @@ +/* 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/. */ + +#ifndef __nsRssIncomingServer_h +#define __nsRssIncomingServer_h + +#include "mozilla/Attributes.h" +#include "nsIRssIncomingServer.h" +#include "nsILocalMailIncomingServer.h" +#include "nsMsgIncomingServer.h" +#include "nsIMsgFolderListener.h" +#include "nsMailboxServer.h" + +class nsRssIncomingServer : public nsMailboxServer, + public nsIRssIncomingServer, + public nsILocalMailIncomingServer, + public nsIMsgFolderListener + +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRSSINCOMINGSERVER + NS_DECL_NSILOCALMAILINCOMINGSERVER + NS_DECL_NSIMSGFOLDERLISTENER + + NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override; + NS_IMETHOD GetSupportsDiskSpace(bool *aSupportsDiskSpace) override; + NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override; + NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override; + NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override; + NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override; + NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override; + + nsRssIncomingServer(); +protected: + virtual ~nsRssIncomingServer(); + nsresult FolderChanged(nsIMsgFolder *aFolder, nsIMsgFolder *aOrigFolder, const char *aAction); + nsresult FillInDataSourcePath(const nsAString& aDataSourceName, nsIFile ** aLocation); + static nsrefcnt gInstanceCount; +}; + +#endif /* __nsRssIncomingServer_h */ diff --git a/mailnews/local/src/nsRssService.cpp b/mailnews/local/src/nsRssService.cpp new file mode 100644 index 000000000..f5caac257 --- /dev/null +++ b/mailnews/local/src/nsRssService.cpp @@ -0,0 +1,130 @@ +/* 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 "nsRssService.h" +#include "nsIRssIncomingServer.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsMailDirServiceDefs.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" + +nsRssService::nsRssService() +{ +} + +nsRssService::~nsRssService() +{ +} + +NS_IMPL_ISUPPORTS(nsRssService, + nsIRssService, + nsIMsgProtocolInfo) + +NS_IMETHODIMP nsRssService::GetDefaultLocalPath(nsIFile * *aDefaultLocalPath) +{ + NS_ENSURE_ARG_POINTER(aDefaultLocalPath); + *aDefaultLocalPath = nullptr; + + nsCOMPtr<nsIFile> localFile; + nsCOMPtr<nsIProperties> dirService(do_GetService("@mozilla.org/file/directory_service;1")); + if (!dirService) return NS_ERROR_FAILURE; + dirService->Get(NS_APP_MAIL_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(localFile)); + if (!localFile) return NS_ERROR_FAILURE; + + bool exists; + nsresult rv = localFile->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + + NS_IF_ADDREF(*aDefaultLocalPath = localFile); + return NS_OK; + +} + +NS_IMETHODIMP nsRssService::SetDefaultLocalPath(nsIFile * aDefaultLocalPath) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssService::GetServerIID(nsIID * *aServerIID) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssService::GetRequiresUsername(bool *aRequiresUsername) +{ + NS_ENSURE_ARG_POINTER(aRequiresUsername); + *aRequiresUsername = false; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssService::GetCanDelete(bool *aCanDelete) +{ + NS_ENSURE_ARG_POINTER(aCanDelete); + *aCanDelete = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp) +{ + NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp); + *aCanLoginAtStartUp = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetCanDuplicate(bool *aCanDuplicate) +{ + NS_ENSURE_ARG_POINTER(aCanDuplicate); + *aCanDuplicate = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetDefaultServerPort(bool isSecure, int32_t *_retval) +{ + *_retval = -1; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetCanGetMessages(bool *aCanGetMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetMessages); + *aCanGetMessages = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages) +{ + NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages); + *aCanGetIncomingMessages = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetDefaultDoBiff(bool *aDefaultDoBiff) +{ + NS_ENSURE_ARG_POINTER(aDefaultDoBiff); + // by default, do biff for RSS feeds + *aDefaultDoBiff = true; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetShowComposeMsgLink(bool *aShowComposeMsgLink) +{ + NS_ENSURE_ARG_POINTER(aShowComposeMsgLink); + *aShowComposeMsgLink = false; + return NS_OK; +} + +NS_IMETHODIMP nsRssService::GetFoldersCreatedAsync(bool *aAsyncCreation) +{ + NS_ENSURE_ARG_POINTER(aAsyncCreation); + *aAsyncCreation = false; + return NS_OK; +} diff --git a/mailnews/local/src/nsRssService.h b/mailnews/local/src/nsRssService.h new file mode 100644 index 000000000..45893bba7 --- /dev/null +++ b/mailnews/local/src/nsRssService.h @@ -0,0 +1,25 @@ +/* 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/. */ + +#ifndef nsRssService_h___ +#define nsRssService_h___ + +#include "nsIRssService.h" +#include "nsIMsgProtocolInfo.h" + +class nsRssService : public nsIMsgProtocolInfo, public nsIRssService +{ +public: + + nsRssService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIRSSSERVICE + NS_DECL_NSIMSGPROTOCOLINFO + +private: + virtual ~nsRssService(); +}; + +#endif /* nsRssService_h___ */ |