/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "msgCore.h" #include "prmem.h" #include "nsImapMailFolder.h" #include "nsIImapService.h" #include "nsIFile.h" #include "nsAnonymousTemporaryFile.h" #include "nsIUrlListener.h" #include "nsCOMPtr.h" #include "nsMsgFolderFlags.h" #include "nsISeekableStream.h" #include "nsThreadUtils.h" #include "nsIImapUrl.h" #include "nsImapUtils.h" #include "nsMsgUtils.h" #include "nsIMsgMailSession.h" #include "nsITransactionManager.h" #include "nsImapUndoTxn.h" #include "../public/nsIImapHostSessionList.h" #include "nsIMsgCopyService.h" #include "nsICopyMessageStreamListener.h" #include "nsImapStringBundle.h" #include "nsIMsgFolderCacheElement.h" #include "nsTextFormatter.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsMsgI18N.h" #include "nsIMsgFilter.h" #include "nsIMsgFilterService.h" #include "nsIMsgSearchCustomTerm.h" #include "nsIMsgSearchTerm.h" #include "nsImapMoveCoalescer.h" #include "nsIPrompt.h" #include "nsIDocShell.h" #include "nsUnicharUtils.h" #include "nsIImapFlagAndUidState.h" #include "nsIImapHeaderXferInfo.h" #include "nsIMessenger.h" #include "nsIMsgSearchAdapter.h" #include "nsIImapMockChannel.h" #include "nsIProgressEventSink.h" #include "nsIMsgWindow.h" #include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... #include "nsIMsgLocalMailFolder.h" #include "nsIMsgOfflineImapOperation.h" #include "nsImapOfflineSync.h" #include "nsIImapMailFolderSink.h" #include "nsIImapServerSink.h" #include "nsIMsgAccountManager.h" #include "nsQuickSort.h" #include "nsIImapMockChannel.h" #include "nsNetUtil.h" #include "nsImapNamespace.h" #include "nsIMsgFolderCompactor.h" #include "nsMsgMessageFlags.h" #include "nsISpamSettings.h" #include #include "nsIMsgMailNewsUrl.h" #include "nsEmbedCID.h" #include "nsIMsgComposeService.h" #include "nsDirectoryServiceDefs.h" #include "nsIDirectoryEnumerator.h" #include "nsIMsgIdentity.h" #include "nsIMsgFolderNotificationService.h" #include "nsNativeCharsetUtils.h" #include "nsIExternalProtocolService.h" #include "nsCExternalHandlerService.h" #include "prprf.h" #include "nsAutoSyncManager.h" #include "nsIMsgFilterCustomAction.h" #include "nsMsgReadStateTxn.h" #include "nsStringEnumerator.h" #include "nsIMsgStatusFeedback.h" #include "nsMsgLineBuffer.h" #include "mozilla/Logging.h" #include "mozilla/Attributes.h" #include "mozilla/SlicedInputStream.h" #include "nsStringStream.h" #include "nsIStreamListener.h" #include "nsITimer.h" #include "nsReadableUtils.h" #include "UrlListener.h" #include "nsIObserverService.h" #define NS_PARSEMAILMSGSTATE_CID \ { /* 2B79AC51-1459-11d3-8097-006008128C4E */ \ 0x2b79ac51, 0x1459, 0x11d3, { \ 0x80, 0x97, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e \ } \ } static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID); #define NS_IIMAPHOSTSESSIONLIST_CID \ { \ 0x479ce8fc, 0xe725, 0x11d2, { \ 0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 \ } \ } static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID); #define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders" using namespace mozilla; extern LazyLogModule gAutoSyncLog; // defined in nsAutoSyncManager.cpp extern LazyLogModule IMAP; // defined in nsImapProtocol.cpp extern LazyLogModule IMAP_CS; // For CONDSTORE, defined in nsImapProtocol.cpp extern LazyLogModule FILTERLOGMODULE; // defined in nsMsgFilterService.cpp LazyLogModule IMAP_KW("IMAP_KW"); // for logging keyword (tag) processing /* Copies the contents of srcDir into destDir. destDir will be created if it doesn't exist. */ static nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir) { bool isDir; nsresult rv = srcDir->IsDirectory(&isDir); NS_ENSURE_SUCCESS(rv, rv); if (!isDir) return NS_ERROR_INVALID_ARG; bool exists; rv = destDir->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists) { rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr dirIterator; rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore = false; while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr dirEntry; rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry)); NS_ENSURE_SUCCESS(rv, rv); if (!dirEntry) continue; rv = dirEntry->IsDirectory(&isDir); NS_ENSURE_SUCCESS(rv, rv); if (isDir) { nsCOMPtr newChild; rv = destDir->Clone(getter_AddRefs(newChild)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString leafName; dirEntry->GetLeafName(leafName); newChild->AppendRelativePath(leafName); rv = newChild->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists) { rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775); NS_ENSURE_SUCCESS(rv, rv); } rv = RecursiveCopy(dirEntry, newChild); } else { rv = dirEntry->CopyTo(destDir, EmptyString()); } NS_ENSURE_SUCCESS(rv, rv); } return rv; } // // nsMsgQuota // NS_IMPL_ISUPPORTS(nsMsgQuota, nsIMsgQuota) nsMsgQuota::nsMsgQuota(const nsACString& aName, const uint64_t& aUsage, const uint64_t& aLimit) : mName(aName), mUsage(aUsage), mLimit(aLimit) {} nsMsgQuota::~nsMsgQuota() {} /** * Note: These quota access function are not called but still must be defined * for the linker. */ NS_IMETHODIMP nsMsgQuota::GetName(nsACString& aName) { aName = mName; return NS_OK; } NS_IMETHODIMP nsMsgQuota::SetName(const nsACString& aName) { mName = aName; return NS_OK; } NS_IMETHODIMP nsMsgQuota::GetUsage(uint64_t* aUsage) { *aUsage = mUsage; return NS_OK; } NS_IMETHODIMP nsMsgQuota::SetUsage(uint64_t aUsage) { mUsage = aUsage; return NS_OK; } NS_IMETHODIMP nsMsgQuota::GetLimit(uint64_t* aLimit) { *aLimit = mLimit; return NS_OK; } NS_IMETHODIMP nsMsgQuota::SetLimit(uint64_t aLimit) { mLimit = aLimit; return NS_OK; } // // nsImapMailFolder // nsImapMailFolder::nsImapMailFolder() : m_initialized(false), m_haveDiscoveredAllFolders(false), m_curMsgUid(0), m_nextMessageByteLength(0), m_urlRunning(false), m_verifiedAsOnlineFolder(false), m_explicitlyVerify(false), m_folderIsNamespace(false), m_folderNeedsSubscribing(false), m_folderNeedsAdded(false), m_folderNeedsACLListed(true), m_performingBiff(false), m_updatingFolder(false), m_applyIncomingFilters(false), m_downloadingFolderForOfflineUse(false), m_filterListRequiresBody(false), m_folderQuotaCommandIssued(false), m_folderQuotaDataIsValid(false) { m_boxFlags = 0; m_uidValidity = kUidUnknown; m_numServerRecentMessages = 0; m_numServerUnseenMessages = 0; m_numServerTotalMessages = 0; m_nextUID = nsMsgKey_None; m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown; m_folderACL = nullptr; m_aclFlags = 0; m_supportedUserFlags = 0; m_namespace = nullptr; m_pendingPlaybackReq = nullptr; } nsImapMailFolder::~nsImapMailFolder() { delete m_folderACL; // cleanup any pending request delete m_pendingPlaybackReq; } NS_IMPL_ADDREF_INHERITED(nsImapMailFolder, nsMsgDBFolder) NS_IMPL_RELEASE_INHERITED(nsImapMailFolder, nsMsgDBFolder) NS_IMPL_QUERY_HEAD(nsImapMailFolder) NS_IMPL_QUERY_BODY(nsIMsgImapMailFolder) NS_IMPL_QUERY_BODY(nsICopyMessageListener) NS_IMPL_QUERY_BODY(nsIImapMailFolderSink) NS_IMPL_QUERY_BODY(nsIImapMessageSink) NS_IMPL_QUERY_BODY(nsIUrlListener) NS_IMPL_QUERY_BODY(nsIMsgFilterHitNotify) NS_IMPL_QUERY_TAIL_INHERITING(nsMsgDBFolder) nsresult nsImapMailFolder::AddDirectorySeparator(nsIFile* path) { if (mURI.Equals(kImapRootURI)) { // don't concat the full separator with .sbd } else { // see if there's a dir with the same name ending with .sbd nsAutoString leafName; path->GetLeafName(leafName); leafName.AppendLiteral(FOLDER_SUFFIX); path->SetLeafName(leafName); } return NS_OK; } static bool nsShouldIgnoreFile(nsString& name) { if (StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(SUMMARY_SUFFIX), nsCaseInsensitiveStringComparator)) { name.SetLength(name.Length() - SUMMARY_SUFFIX_LENGTH); // truncate the string return false; } return true; } nsresult nsImapMailFolder::CreateChildFromURI(const nsACString& uri, nsIMsgFolder** folder) { nsImapMailFolder* newFolder = new nsImapMailFolder; newFolder->Init(uri); NS_ADDREF(*folder = newFolder); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::AddSubfolder(const nsAString& aName, nsIMsgFolder** aChild) { NS_ENSURE_ARG_POINTER(aChild); int32_t flags = 0; nsresult rv; nsAutoCString uri(mURI); uri.Append('/'); nsAutoCString escapedName; rv = NS_MsgEscapeEncodeURLPath(aName, escapedName); NS_ENSURE_SUCCESS(rv, rv); uri += escapedName.get(); nsCOMPtr msgFolder; rv = GetChildWithURI(uri, false /*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder)); if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS; nsCOMPtr folder; rv = GetOrCreateFolder(uri, getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // Ensure the containing dir exists. nsCOMPtr path; rv = CreateDirectoryForFolder(getter_AddRefs(path)); NS_ENSURE_SUCCESS(rv, rv); folder->GetFlags((uint32_t*)&flags); flags |= nsMsgFolderFlags::Mail; nsCOMPtr imapServer; GetImapIncomingServer(getter_AddRefs(imapServer)); if (imapServer) { bool setNewFoldersForOffline = false; rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline); if (NS_SUCCEEDED(rv) && setNewFoldersForOffline) flags |= nsMsgFolderFlags::Offline; } folder->SetParent(this); folder->SetFlags(flags); mSubFolders.AppendObject(folder); folder.forget(aChild); // New child needs to inherit hierarchyDelimiter. nsCOMPtr imapChild = do_QueryInterface(*aChild); if (imapChild) { imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter); } NotifyFolderAdded(*aChild); return rv; } // Creates a new child nsIMsgFolder locally, with no IMAP traffic. nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString& name, nsIFile* dbPath, nsIMsgFolder** child, bool brandNew) { NS_ENSURE_ARG_POINTER(child); nsresult rv; nsAutoCString uri(mURI); uri.Append('/'); AppendUTF16toUTF8(name, uri); bool isServer; rv = GetIsServer(&isServer); NS_ENSURE_SUCCESS(rv, rv); bool isInbox = isServer && name.LowerCaseEqualsLiteral("inbox"); // will make sure mSubFolders does not have duplicates because of bogus msf // files. nsCOMPtr msgFolder; rv = GetChildWithURI(uri, false /*deep*/, isInbox /*case Insensitive*/, getter_AddRefs(msgFolder)); if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS; nsCOMPtr folder; rv = GetOrCreateFolder(uri, getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv, rv); folder->SetFilePath(dbPath); nsCOMPtr imapFolder = do_QueryInterface(folder, &rv); mozilla::Unused << imapFolder; NS_ENSURE_SUCCESS(rv, rv); uint32_t flags = 0; folder->GetFlags(&flags); folder->SetParent(this); flags |= nsMsgFolderFlags::Mail; uint32_t pFlags; GetFlags(&pFlags); bool isParentInbox = pFlags & nsMsgFolderFlags::Inbox; nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); // Only set these if these are top level children or parent is inbox if (isInbox) flags |= nsMsgFolderFlags::Inbox; else if (isServer || isParentInbox) { nsMsgImapDeleteModel deleteModel; imapServer->GetDeleteModel(&deleteModel); if (deleteModel == nsMsgImapDeleteModels::MoveToTrash) { nsAutoString trashName; GetTrashFolderName(trashName); if (name.Equals(trashName)) flags |= nsMsgFolderFlags::Trash; } } // Make the folder offline if it is newly created and the offline_download // pref is true, unless it's the Trash or Junk folder. if (brandNew && !(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) { bool setNewFoldersForOffline = false; rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline); if (NS_SUCCEEDED(rv) && setNewFoldersForOffline) flags |= nsMsgFolderFlags::Offline; } folder->SetFlags(flags); if (folder) mSubFolders.AppendObject(folder); folder.forget(child); return NS_OK; } // Create child nsIMsgFolders by scanning the filesystem to find .msf files. // No IMAP traffic. nsresult nsImapMailFolder::CreateSubFolders(nsIFile* path) { nsresult rv = NS_OK; nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr directoryEnumerator; rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); NS_ENSURE_SUCCESS(rv, rv); // For each .msf file in the directory... bool hasMore = false; while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr currentFolderPath; rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFolderPath)); if (NS_FAILED(rv) || !currentFolderPath) continue; nsAutoString currentFolderNameStr; // online name nsAutoString currentFolderDBNameStr; // possibly munged name currentFolderPath->GetLeafName(currentFolderNameStr); // Skip if not an .msf file. // (NOTE: nsShouldIgnoreFile() strips the trailing ".msf" here) if (nsShouldIgnoreFile(currentFolderNameStr)) continue; // OK, here we need to get the online name from the folder cache if we can. // If we can, use that to create the sub-folder nsCOMPtr curFolder = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); dbFile->InitWithFile(currentFolderPath); curFolder->InitWithFile(currentFolderPath); // don't strip off the .msf in currentFolderPath. currentFolderPath->SetLeafName(currentFolderNameStr); currentFolderDBNameStr = currentFolderNameStr; nsAutoString utfLeafName = currentFolderNameStr; if (curFolder) { nsCOMPtr cacheElement; rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement)); if (NS_SUCCEEDED(rv) && cacheElement) { nsCString onlineFullUtfName; uint32_t folderFlags; rv = cacheElement->GetCachedUInt32("flags", &folderFlags); if (NS_SUCCEEDED(rv) && folderFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders continue; int32_t hierarchyDelimiter; rv = cacheElement->GetCachedInt32("hierDelim", &hierarchyDelimiter); if (NS_SUCCEEDED(rv) && hierarchyDelimiter == kOnlineHierarchySeparatorUnknown) { currentFolderPath->Remove(false); continue; // blow away .msf files for folders with unknown delimiter. } rv = cacheElement->GetCachedString("onlineName", onlineFullUtfName); if (NS_SUCCEEDED(rv) && !onlineFullUtfName.IsEmpty()) { CopyFolderNameToUTF16(onlineFullUtfName, currentFolderNameStr); char delimiter = 0; GetHierarchyDelimiter(&delimiter); int32_t leafPos = currentFolderNameStr.RFindChar(delimiter); if (leafPos > 0) currentFolderNameStr.Cut(0, leafPos + 1); // Take the full online name, and determine the leaf name. CopyUTF8toUTF16(onlineFullUtfName, utfLeafName); leafPos = utfLeafName.RFindChar(delimiter); if (leafPos > 0) utfLeafName.Cut(0, leafPos + 1); } } } // make the imap folder remember the file spec it was created with. nsCOMPtr msfFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); msfFilePath->InitWithFile(currentFolderPath); if (NS_SUCCEEDED(rv) && msfFilePath) { // leaf name is the db name w/o .msf (nsShouldIgnoreFile strips it off) // so this trims the .msf off the file spec. msfFilePath->SetLeafName(currentFolderDBNameStr); } // Use the name as the uri for the folder. nsCOMPtr child; AddSubfolderWithPath(utfLeafName, msfFilePath, getter_AddRefs(child)); if (child) { // use the unicode name as the "pretty" name. Set it so it won't be // automatically computed from the URI. if (!currentFolderNameStr.IsEmpty()) child->SetPrettyName(currentFolderNameStr); child->SetMsgDatabase(nullptr); } } return rv; } NS_IMETHODIMP nsImapMailFolder::GetSubFolders( nsTArray>& folders) { bool isServer; nsresult rv = GetIsServer(&isServer); NS_ENSURE_SUCCESS(rv, rv); if (!m_initialized) { nsCOMPtr pathFile; rv = GetFilePath(getter_AddRefs(pathFile)); if (NS_FAILED(rv)) return rv; // host directory does not need .sbd tacked on if (!isServer) { rv = AddDirectorySeparator(pathFile); if (NS_FAILED(rv)) return rv; } m_initialized = true; // need to set this here to avoid infinite recursion // from CreateSubfolders. // we have to treat the root folder specially, because it's name // doesn't end with .sbd int32_t newFlags = nsMsgFolderFlags::Mail; bool isDirectory = false; pathFile->IsDirectory(&isDirectory); if (isDirectory) { newFlags |= (nsMsgFolderFlags::Directory | nsMsgFolderFlags::Elided); if (!mIsServer) SetFlag(newFlags); rv = CreateSubFolders(pathFile); } if (isServer) { nsCOMPtr inboxFolder; GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder)); if (!inboxFolder) { // create an inbox if we don't have one. CreateClientSubfolderInfo("INBOX"_ns, kOnlineHierarchySeparatorUnknown, 0, true); } } // Force initialisation recursively. for (nsIMsgFolder* f : mSubFolders) { nsTArray> dummy; rv = f->GetSubFolders(dummy); if (NS_FAILED(rv)) { break; } } UpdateSummaryTotals(false); if (NS_FAILED(rv)) return rv; } return nsMsgDBFolder::GetSubFolders(folders); } // Makes sure the database is open and exists. If the database is valid then // returns NS_OK. Otherwise returns a failure error value. nsresult nsImapMailFolder::GetDatabase() { nsresult rv = NS_OK; if (!mDatabase) { nsCOMPtr msgDBService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // Create the database, blowing it away if it needs to be rebuilt rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase)); if (NS_FAILED(rv)) rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); NS_ENSURE_SUCCESS(rv, rv); // UpdateNewMessages/UpdateSummaryTotals can null mDatabase, so we save a // local copy nsCOMPtr database(mDatabase); UpdateNewMessages(); if (mAddListener) database->AddListener(this); UpdateSummaryTotals(true); mDatabase = database; } return rv; } NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow* inMsgWindow) { return UpdateFolderWithListener(inMsgWindow, nullptr); } NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener( nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener) { nsresult rv; // If this is the inbox, filters will be applied. Otherwise, we test the // inherited folder property "applyIncomingFilters" (which defaults to empty). // If this inherited property has the string value "true", we will apply // filters even if this is not the inbox folder. nsCString applyIncomingFilters; GetInheritedStringProperty("applyIncomingFilters", applyIncomingFilters); m_applyIncomingFilters = applyIncomingFilters.EqualsLiteral("true"); nsString folderName; GetPrettyName(folderName); MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("(Imap) nsImapMailFolder::UpdateFolderWithListener() on folder '%s'", NS_ConvertUTF16toUTF8(folderName).get())); if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) { MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Preparing filter run on folder '%s'", NS_ConvertUTF16toUTF8(folderName).get())); if (!m_filterList) { rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList)); if (NS_FAILED(rv)) { MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, ("(Imap) Loading of filter list failed")); } } // if there's no msg window, but someone is updating the inbox, we're // doing something biff-like, and may download headers, so make biff notify. if (!aMsgWindow && mFlags & nsMsgFolderFlags::Inbox) SetPerformingBiff(true); } if (m_filterList) { nsCString listId; m_filterList->GetListId(listId); MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Preparing filter list %s", listId.get())); nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); bool canFileMessagesOnServer = true; rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer); // the mdn filter is for filing return receipts into the sent folder // some servers (like AOL mail servers) // can't file to the sent folder, so we don't add the filter for those // servers if (canFileMessagesOnServer) { rv = server->ConfigureTemporaryFilters(m_filterList); NS_ENSURE_SUCCESS(rv, rv); } // If a body filter is enabled for an offline folder, delay the filter // application until after message has been downloaded. m_filterListRequiresBody = false; if (mFlags & nsMsgFolderFlags::Offline) { nsCOMPtr filterService = do_GetService("@mozilla.org/messenger/services/filters;1", &rv); uint32_t filterCount = 0; m_filterList->GetFilterCount(&filterCount); for (uint32_t index = 0; index < filterCount && !m_filterListRequiresBody; ++index) { nsCOMPtr filter; m_filterList->GetFilterAt(index, getter_AddRefs(filter)); if (!filter) continue; nsMsgFilterTypeType filterType; filter->GetFilterType(&filterType); if (!(filterType & nsMsgFilterType::Incoming)) continue; bool enabled = false; filter->GetEnabled(&enabled); if (!enabled) continue; nsTArray> searchTerms; filter->GetSearchTerms(searchTerms); for (nsIMsgSearchTerm* term : searchTerms) { nsMsgSearchAttribValue attrib; rv = term->GetAttrib(&attrib); NS_ENSURE_SUCCESS(rv, rv); if (attrib == nsMsgSearchAttrib::Body) m_filterListRequiresBody = true; else if (attrib == nsMsgSearchAttrib::Custom) { nsAutoCString customId; rv = term->GetCustomId(customId); nsCOMPtr customTerm; if (NS_SUCCEEDED(rv) && filterService) rv = filterService->GetCustomTerm(customId, getter_AddRefs(customTerm)); bool needsBody = false; if (NS_SUCCEEDED(rv) && customTerm) rv = customTerm->GetNeedsBody(&needsBody); if (NS_SUCCEEDED(rv) && needsBody) m_filterListRequiresBody = true; } if (m_filterListRequiresBody) { break; } } // Also check if filter actions need the body, as this // is supported in custom actions. uint32_t numActions = 0; filter->GetActionCount(&numActions); for (uint32_t actionIndex = 0; actionIndex < numActions && !m_filterListRequiresBody; actionIndex++) { nsCOMPtr action; rv = filter->GetActionAt(actionIndex, getter_AddRefs(action)); if (NS_FAILED(rv) || !action) continue; nsCOMPtr customAction; rv = action->GetCustomAction(getter_AddRefs(customAction)); if (NS_FAILED(rv) || !customAction) continue; bool needsBody = false; customAction->GetNeedsBody(&needsBody); if (needsBody) m_filterListRequiresBody = true; } } } MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Filters require the message body: %s", (m_filterListRequiresBody ? "true" : "false"))); } bool isServer; rv = GetIsServer(&isServer); if (NS_SUCCEEDED(rv) && isServer) { if (!m_haveDiscoveredAllFolders) { bool hasSubFolders = false; GetHasSubFolders(&hasSubFolders); if (!hasSubFolders) { rv = CreateClientSubfolderInfo( "Inbox"_ns, kOnlineHierarchySeparatorUnknown, 0, false); NS_ENSURE_SUCCESS(rv, rv); } m_haveDiscoveredAllFolders = true; } } rv = GetDatabase(); if (NS_FAILED(rv)) { ThrowAlertMsg("errorGettingDB", aMsgWindow); return rv; } bool hasOfflineEvents = false; GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents); if (!WeAreOffline()) { if (hasOfflineEvents) { // hold a reference to the offline sync object. If ProcessNextOperation // runs a url, a reference will be added to it. Otherwise, it will get // destroyed when the refptr goes out of scope. RefPtr goOnline = new nsImapOfflineSync(); goOnline->Init(aMsgWindow, this, this, false); if (goOnline) { m_urlListener = aUrlListener; return goOnline->ProcessNextOperation(); } } } // Check it we're password protecting the local store. if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE; bool canOpenThisFolder = true; GetCanOpenFolder(&canOpenThisFolder); // Don't run select if we can't select the folder... if (!m_urlRunning && canOpenThisFolder && !isServer) { nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); /* Do a discovery in its own url if needed. Do before SELECT url. */ nsCOMPtr hostSession = do_GetService(kCImapHostSessionList, &rv); if (NS_SUCCEEDED(rv) && hostSession) { bool foundMailboxesAlready = false; nsCString serverKey; GetServerKey(serverKey); hostSession->GetHaveWeEverDiscoveredFoldersForHost(serverKey.get(), foundMailboxesAlready); if (!foundMailboxesAlready) { bool discoveryInProgress = false; // See if discovery in progress and not yet finished. hostSession->GetDiscoveryForHostInProgress(serverKey.get(), discoveryInProgress); if (!discoveryInProgress) { nsCOMPtr rootFolder; rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && rootFolder) { rv = imapService->DiscoverAllFolders(rootFolder, this, aMsgWindow); if (NS_SUCCEEDED(rv)) hostSession->SetDiscoveryForHostInProgress(serverKey.get(), true); } } } } nsCOMPtr url; rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow, getter_AddRefs(url)); if (NS_SUCCEEDED(rv)) { m_urlRunning = true; m_updatingFolder = true; } if (url) { nsCOMPtr mailnewsUrl = do_QueryInterface(url, &rv); NS_ENSURE_SUCCESS(rv, rv); mailnewsUrl->RegisterListener(this); m_urlListener = aUrlListener; } // Allow IMAP folder auto-compact to occur when online or offline. if (aMsgWindow) AutoCompact(aMsgWindow); if (rv == NS_MSG_ERROR_OFFLINE || rv == NS_BINDING_ABORTED) { rv = NS_OK; NotifyFolderEvent(kFolderLoaded); } } else { // Tell the front end that the folder is loaded if we're not going to // actually run a url. if (!m_updatingFolder) // if we're already running an update url, we'll let // that one send the folder loaded NotifyFolderEvent(kFolderLoaded); } return rv; } NS_IMETHODIMP nsImapMailFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow* msgWindow) { if (folderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME; nsresult rv; nsAutoString trashName; GetTrashFolderName(trashName); if (folderName.Equals(trashName)) // Trash , a special folder { ThrowAlertMsg("folderExists", msgWindow); return NS_MSG_FOLDER_EXISTS; } if (mIsServer && folderName.LowerCaseEqualsLiteral("inbox")) // Inbox, a special folder { ThrowAlertMsg("folderExists", msgWindow); return NS_MSG_FOLDER_EXISTS; } nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr url; return imapService->CreateFolder(this, folderName, this, getter_AddRefs(url)); } NS_IMETHODIMP nsImapMailFolder::CreateClientSubfolderInfo( const nsACString& folderName, char hierarchyDelimiter, int32_t flags, bool suppressNotification) { nsresult rv = NS_OK; // Get a directory based on our current path. nsCOMPtr path; rv = CreateDirectoryForFolder(getter_AddRefs(path)); if (NS_FAILED(rv)) return rv; NS_ConvertUTF8toUTF16 leafName(folderName); nsAutoString folderNameStr; nsAutoString parentName = leafName; // use RFind, because folder can start with a delimiter and // not be a leaf folder. int32_t folderStart = leafName.RFindChar('/'); if (folderStart > 0) { nsCOMPtr imapFolder; nsAutoCString uri(mURI); leafName.Assign(Substring(parentName, folderStart + 1)); parentName.SetLength(folderStart); rv = CreateDirectoryForFolder(getter_AddRefs(path)); NS_ENSURE_SUCCESS(rv, rv); uri.Append('/'); uri.Append(NS_ConvertUTF16toUTF8(parentName)); nsCOMPtr folder; rv = GetOrCreateFolder(uri, getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv, rv); imapFolder = do_QueryInterface(folder, &rv); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString leafnameC; CopyUTF16toUTF8(leafName, leafnameC); return imapFolder->CreateClientSubfolderInfo(leafnameC, hierarchyDelimiter, flags, suppressNotification); } // if we get here, it's really a leaf, and "this" is the parent. folderNameStr = leafName; // Create an empty database for this mail folder, set its name from the user nsCOMPtr mailDBFactory; nsCOMPtr child; nsCOMPtr msgDBService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr unusedDB; nsCOMPtr dbFile; // warning, path will be changed rv = CreateFileForDB(folderNameStr, path, getter_AddRefs(dbFile)); NS_ENSURE_SUCCESS(rv, rv); // Now let's create the actual new folder rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child), true); NS_ENSURE_SUCCESS(rv, rv); rv = msgDBService->OpenMailDBFromFile(dbFile, child, true, true, getter_AddRefs(unusedDB)); if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) rv = NS_OK; if (NS_SUCCEEDED(rv) && unusedDB) { // need to set the folder name nsCOMPtr folderInfo; rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); nsCOMPtr imapFolder = do_QueryInterface(child, &rv); if (NS_SUCCEEDED(rv)) { nsAutoCString onlineName(m_onlineFolderName); if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter); onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr)); imapFolder->SetVerifiedAsOnlineFolder(true); imapFolder->SetOnlineName(onlineName); imapFolder->SetHierarchyDelimiter(hierarchyDelimiter); imapFolder->SetBoxFlags(flags); // Now that the child is created and the boxflags are set we can be sure // all special folder flags are known. The child may get its flags already // in AddSubfolderWithPath if they were in FolderCache, but that's // not always the case. uint32_t flags = 0; child->GetFlags(&flags); // Set the offline use flag for the newly created folder if the // offline_download preference is true, unless it's the Trash or Junk // folder. if (!(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) { nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); bool setNewFoldersForOffline = false; rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline); if (NS_SUCCEEDED(rv) && setNewFoldersForOffline) flags |= nsMsgFolderFlags::Offline; } else { flags &= ~nsMsgFolderFlags::Offline; // clear offline flag if set } flags |= nsMsgFolderFlags::Elided; child->SetFlags(flags); nsString unicodeName; rv = CopyFolderNameToUTF16(nsCString(folderName), unicodeName); if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName); // store the online name as the mailbox name in the db folder info // I don't think anyone uses the mailbox name, so we'll use it // to restore the online name when blowing away an imap db. if (folderInfo) folderInfo->SetMailboxName(NS_ConvertUTF8toUTF16(onlineName)); } unusedDB->SetSummaryValid(true); unusedDB->Commit(nsMsgDBCommitType::kLargeCommit); unusedDB->Close(true); // don't want to hold onto this newly created db. child->SetMsgDatabase(nullptr); } if (!suppressNotification) { if (NS_SUCCEEDED(rv) && child) { NotifyFolderAdded(child); child->NotifyFolderEvent(kFolderCreateCompleted); nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) notifier->NotifyFolderAdded(child); } else { NotifyFolderEvent(kFolderCreateFailed); } } return rv; } NS_IMETHODIMP nsImapMailFolder::List() { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->ListFolder(this, this); } NS_IMETHODIMP nsImapMailFolder::RemoveLocalSelf() { // Kill the local folder and its storage. return nsMsgDBFolder::DeleteSelf(nullptr); } NS_IMETHODIMP nsImapMailFolder::CreateStorageIfMissing( nsIUrlListener* urlListener) { nsresult rv = NS_OK; nsCOMPtr msgParent; GetParent(getter_AddRefs(msgParent)); // parent is probably not set because *this* was probably created by rdf // and not by folder discovery. So, we have to compute the parent. if (!msgParent) { nsAutoCString folderName(mURI); int32_t leafPos = folderName.RFindChar('/'); nsAutoCString parentName(folderName); if (leafPos > 0) { // If there is a hierarchy, there is a parent. // Don't strip off slash if it's the first character parentName.SetLength(leafPos); rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent)); NS_ENSURE_SUCCESS(rv, rv); } } if (msgParent) { nsString folderName; GetName(folderName); nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); imapService->EnsureFolderExists(msgParent, folderName, nullptr, urlListener); } return rv; } NS_IMETHODIMP nsImapMailFolder::GetVerifiedAsOnlineFolder( bool* aVerifiedAsOnlineFolder) { NS_ENSURE_ARG_POINTER(aVerifiedAsOnlineFolder); *aVerifiedAsOnlineFolder = m_verifiedAsOnlineFolder; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetVerifiedAsOnlineFolder( bool aVerifiedAsOnlineFolder) { m_verifiedAsOnlineFolder = aVerifiedAsOnlineFolder; // mark ancestors as verified as well if (aVerifiedAsOnlineFolder) { nsCOMPtr parent; do { GetParent(getter_AddRefs(parent)); if (parent) { nsCOMPtr imapParent = do_QueryInterface(parent); if (imapParent) { bool verifiedOnline; imapParent->GetVerifiedAsOnlineFolder(&verifiedOnline); if (verifiedOnline) break; imapParent->SetVerifiedAsOnlineFolder(true); } } } while (parent); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetOnlineDelimiter(char* onlineDelimiter) { return GetHierarchyDelimiter(onlineDelimiter); } NS_IMETHODIMP nsImapMailFolder::SetHierarchyDelimiter( char aHierarchyDelimiter) { m_hierarchyDelimiter = aHierarchyDelimiter; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetHierarchyDelimiter( char* aHierarchyDelimiter) { NS_ENSURE_ARG_POINTER(aHierarchyDelimiter); if (mIsServer) { // if it's the root folder, we don't know the delimiter. So look at the // first child. int32_t count = mSubFolders.Count(); if (count > 0) { nsCOMPtr childFolder( do_QueryInterface(mSubFolders[0])); if (childFolder) { nsresult rv = childFolder->GetHierarchyDelimiter(aHierarchyDelimiter); // some code uses m_hierarchyDelimiter directly, so we should set it. m_hierarchyDelimiter = *aHierarchyDelimiter; return rv; } } } ReadDBFolderInfo(false); // update cache first. *aHierarchyDelimiter = m_hierarchyDelimiter; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetBoxFlags(int32_t aBoxFlags) { ReadDBFolderInfo(false); m_boxFlags = aBoxFlags; uint32_t newFlags = mFlags; newFlags |= nsMsgFolderFlags::ImapBox; if (m_boxFlags & kNoinferiors) newFlags |= nsMsgFolderFlags::ImapNoinferiors; else newFlags &= ~nsMsgFolderFlags::ImapNoinferiors; if (m_boxFlags & kNoselect) newFlags |= nsMsgFolderFlags::ImapNoselect; else newFlags &= ~nsMsgFolderFlags::ImapNoselect; if (m_boxFlags & kPublicMailbox) newFlags |= nsMsgFolderFlags::ImapPublic; else newFlags &= ~nsMsgFolderFlags::ImapPublic; if (m_boxFlags & kOtherUsersMailbox) newFlags |= nsMsgFolderFlags::ImapOtherUser; else newFlags &= ~nsMsgFolderFlags::ImapOtherUser; if (m_boxFlags & kPersonalMailbox) newFlags |= nsMsgFolderFlags::ImapPersonal; else newFlags &= ~nsMsgFolderFlags::ImapPersonal; // The following are all flags returned by XLIST. // nsImapIncomingServer::DiscoveryDone checks for these folders. if (m_boxFlags & kImapDrafts) newFlags |= nsMsgFolderFlags::Drafts; if (m_boxFlags & kImapSpam) newFlags |= nsMsgFolderFlags::Junk; if (m_boxFlags & kImapSent) newFlags |= nsMsgFolderFlags::SentMail; if (m_boxFlags & kImapInbox) newFlags |= nsMsgFolderFlags::Inbox; if (m_boxFlags & kImapXListTrash) { nsCOMPtr imapServer; nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash; (void)GetImapIncomingServer(getter_AddRefs(imapServer)); if (imapServer) imapServer->GetDeleteModel(&deleteModel); if (deleteModel == nsMsgImapDeleteModels::MoveToTrash) newFlags |= nsMsgFolderFlags::Trash; } // Treat the GMail all mail folder as the archive folder. if (m_boxFlags & (kImapAllMail | kImapArchive)) newFlags |= nsMsgFolderFlags::Archive; SetFlags(newFlags); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetBoxFlags(int32_t* aBoxFlags) { NS_ENSURE_ARG_POINTER(aBoxFlags); *aBoxFlags = m_boxFlags; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetExplicitlyVerify(bool* aExplicitlyVerify) { NS_ENSURE_ARG_POINTER(aExplicitlyVerify); *aExplicitlyVerify = m_explicitlyVerify; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetExplicitlyVerify(bool aExplicitlyVerify) { m_explicitlyVerify = aExplicitlyVerify; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetNoSelect(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); return GetFlag(nsMsgFolderFlags::ImapNoselect, aResult); } NS_IMETHODIMP nsImapMailFolder::ApplyRetentionSettings() { int32_t numDaysToKeepOfflineMsgs = -1; // Check if we've limited the offline storage by age. nsCOMPtr imapServer; nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs); nsCOMPtr holdDBOpen; if (numDaysToKeepOfflineMsgs > 0) { bool dbWasCached = mDatabase != nullptr; rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr hdrs; rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore = false; PRTime cutOffDay = MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs); // so now cutOffDay is the PRTime cut-off point. Any offline msg with // a date less than that will get marked for pending removal. while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr header; rv = hdrs->GetNext(getter_AddRefs(header)); NS_ENSURE_SUCCESS(rv, rv); uint32_t msgFlags; PRTime msgDate; header->GetFlags(&msgFlags); if (msgFlags & nsMsgMessageFlags::Offline) { header->GetDate(&msgDate); MarkPendingRemoval(header, msgDate < cutOffDay); // I'm horribly tempted to break out of the loop if we've found // a message after the cut-off date, because messages will most likely // be in date order in the db, but there are always edge cases. } } if (!dbWasCached) { holdDBOpen = mDatabase; mDatabase = nullptr; } } return nsMsgDBFolder::ApplyRetentionSettings(); } /** * The listener will get called when both the online expunge and the offline * store compaction are finished (if the latter is needed). */ nsresult nsImapMailFolder::ExpungeAndCompact(nsIUrlListener* aListener, nsIMsgWindow* aMsgWindow) { GetDatabase(); // now's a good time to apply the retention settings. If we do delete any // messages, the expunge is going to have to wait until the delete to // finish before it can run, but the multiple-connection protection code // should handle that. if (mDatabase) ApplyRetentionSettings(); // Things to hold in existence until both expunge and compact are complete. RefPtr folder = this; nsCOMPtr finalListener = aListener; nsCOMPtr msgWindow = aMsgWindow; // doCompact implements OnStopRunningUrl() // NOTE: The caller will be expecting that their listener will be invoked, so // we need to be careful that all execution paths in here do that. We either // call it directly, or pass it along to the foldercompactor to call. auto doCompact = [folder, finalListener, msgWindow]( nsIURI* url, nsresult status) -> nsresult { nsCOMPtr msgStore; nsresult rv = folder->GetMsgStore(getter_AddRefs(msgStore)); if (NS_FAILED(rv)) { if (finalListener) { return finalListener->OnStopRunningUrl(nullptr, rv); } return rv; } bool storeSupportsCompaction; msgStore->GetSupportsCompaction(&storeSupportsCompaction); if (storeSupportsCompaction && folder->mFlags & nsMsgFolderFlags::Offline) { nsCOMPtr folderCompactor = do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv); if (NS_FAILED(rv)) { if (finalListener) { return finalListener->OnStopRunningUrl(nullptr, rv); } return rv; } return folderCompactor->CompactFolders({folder}, finalListener, msgWindow); } // Not going to run a compaction, so signal that we're all done. if (finalListener) { return finalListener->OnStopRunningUrl(nullptr, NS_OK); } return NS_OK; }; if (WeAreOffline()) { // Can't run an expunge. Kick off the next stage (compact) immediately. return doCompact(nullptr, NS_OK); } // Run the expunge, followed by the compaction. RefPtr expungeListener = new UrlListener(); expungeListener->mStopFn = doCompact; return Expunge(expungeListener, aMsgWindow); } // IMAP compact implies an Expunge. NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener* aListener, nsIMsgWindow* aMsgWindow) { return ExpungeAndCompact(aListener, aMsgWindow); } NS_IMETHODIMP nsImapMailFolder::NotifyCompactCompleted() { return NS_OK; } NS_IMETHODIMP nsImapMailFolder::MarkPendingRemoval(nsIMsgDBHdr* aHdr, bool aMark) { NS_ENSURE_ARG_POINTER(aHdr); uint32_t offlineMessageSize; aHdr->GetOfflineMessageSize(&offlineMessageSize); aHdr->SetStringProperty("pendingRemoval", aMark ? "1"_ns : ""_ns); if (!aMark) return NS_OK; nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr dbFolderInfo; rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); NS_ENSURE_SUCCESS(rv, rv); return dbFolderInfo->ChangeExpungedBytes(offlineMessageSize); } NS_IMETHODIMP nsImapMailFolder::Expunge(nsIUrlListener* aListener, nsIMsgWindow* aMsgWindow) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->Expunge(this, aListener, aMsgWindow); } NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener* aListener, nsIMsgWindow* aMsgWindow) { nsresult rv; nsCOMPtr folderCompactor = do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr rootFolder; rv = GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgWindow = aMsgWindow; // Set up a callable which will start the compaction phase. auto doCompact = [folderCompactor, rootFolder, listener = nsCOMPtr(aListener), msgWindow]() { // Collect all the compactable folders. nsTArray> foldersToCompact; nsTArray> allDescendants; rootFolder->GetDescendants(allDescendants); for (auto folder : allDescendants) { uint32_t flags; folder->GetFlags(&flags); if (flags & (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect)) { continue; } // Folder can be compacted? nsCOMPtr msgStore; folder->GetMsgStore(getter_AddRefs(msgStore)); if (!msgStore) { continue; } bool storeSupportsCompaction; msgStore->GetSupportsCompaction(&storeSupportsCompaction); if (storeSupportsCompaction) { foldersToCompact.AppendElement(folder); } } nsresult rv = folderCompactor->CompactFolders(foldersToCompact, listener, msgWindow); if (NS_FAILED(rv) && listener) { // Make sure the listener hears about the failure. listener->OnStopRunningUrl(nullptr, rv); } }; // Collect all the expungeable folders. nsTArray> foldersToExpunge; nsTArray> allDescendants; rootFolder->GetDescendants(allDescendants); for (auto folder : allDescendants) { nsCOMPtr imapFolder(do_QueryInterface(folder)); if (!imapFolder) { continue; } uint32_t folderFlags; folder->GetFlags(&folderFlags); if (!(folderFlags & (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))) { foldersToExpunge.AppendElement(imapFolder); } } if (!WeAreOffline() && !foldersToExpunge.IsEmpty()) { // Kick off expunge on all the folders (the IMAP protocol will handle // queuing them up as needed). // A listener to track the completed expunges. RefPtr l = new UrlListener(); l->mStopFn = [expungeCount = foldersToExpunge.Length(), doCompact]( nsIURI* url, nsresult status) mutable -> nsresult { // NOTE: we're ignoring expunge result code - nothing much we can do // here to recover, so just plough on. --expungeCount; if (expungeCount == 0) { // All the expunges are done so start compacting. doCompact(); } return NS_OK; }; // Go! for (auto& imapFolder : foldersToExpunge) { rv = imapFolder->Expunge(l, aMsgWindow); if (NS_FAILED(rv)) { // Make sure expungeCount is kept in sync! l->OnStopRunningUrl(nullptr, rv); } } } else { // No expunging. Start the compaction immediately. doCompact(); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener* aListener, nsIMsgWindow* aMsgWindow) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr uri; rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri)); if (uri && !aMsgWindow) { nsCOMPtr mailNewsUrl = do_QueryInterface(uri, &rv); NS_ENSURE_SUCCESS(rv, rv); // if no msg window, we won't put up error messages (this is almost // certainly a biff-inspired status) mailNewsUrl->SetSuppressErrorMsgs(true); } return rv; } NS_IMETHODIMP nsImapMailFolder::EmptyTrash(nsIUrlListener* aListener) { nsCOMPtr trashFolder; nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder)); if (NS_SUCCEEDED(rv)) { nsCOMPtr accountManager = do_GetService("@mozilla.org/messenger/account-manager;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // if we are emptying trash on exit and we are an aol server then don't // perform this operation because it's causing a hang that we haven't been // able to figure out yet this is an rtm fix and we'll look for the right // solution post rtm. bool empytingOnExit = false; accountManager->GetEmptyTrashInProgress(&empytingOnExit); if (empytingOnExit) { nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); if (imapServer) { bool isAOLServer = false; imapServer->GetIsAOLServer(&isAOLServer); if (isAOLServer) return NS_ERROR_FAILURE; // we will not be performing an empty // trash.... } // if we fetched an imap server } // if emptying trash on exit which is done through the account manager. if (WeAreOffline()) { nsCOMPtr trashDB; rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB)); if (trashDB) { nsCOMPtr opsDb = do_QueryInterface(trashDB, &rv); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey fakeKey; opsDb->GetNextFakeOfflineMsgKey(&fakeKey); nsCOMPtr op; rv = opsDb->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op)); trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents); op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs); } return rv; } nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); if (aListener) rv = imapService->DeleteAllMessages(trashFolder, aListener); else { nsCOMPtr urlListener = do_QueryInterface(trashFolder); rv = imapService->DeleteAllMessages(trashFolder, urlListener); } // Return an error if this failed. We want the empty trash on exit code // to know if this fails so that it doesn't block waiting for empty trash to // finish. NS_ENSURE_SUCCESS(rv, rv); // Delete any subfolders under Trash. nsTArray> subFolders; rv = trashFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); while (!subFolders.IsEmpty()) { RefPtr f = subFolders.PopLastElement(); rv = trashFolder->PropagateDelete(f, true); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr transferInfo; rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); NS_ENSURE_SUCCESS(rv, rv); // Bulk-delete all the messages by deleting the msf file and storage. // This is a little kludgy. rv = trashFolder->DeleteStorage(); NS_ENSURE_SUCCESS(rv, rv); if (transferInfo) trashFolder->SetDBTransferInfo(transferInfo); trashFolder->SetSizeOnDisk(0); // The trash folder has effectively been deleted. nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) notifier->NotifyFolderDeleted(trashFolder); return NS_OK; } return rv; } NS_IMETHODIMP nsImapMailFolder::DeleteStorage() { nsresult rv = nsMsgDBFolder::DeleteStorage(); // Should notify nsIMsgFolderListeners about the folder getting deleted? return rv; } NS_IMETHODIMP nsImapMailFolder::Rename(const nsAString& newName, nsIMsgWindow* msgWindow) { if (mFlags & nsMsgFolderFlags::Virtual) return nsMsgDBFolder::Rename(newName, msgWindow); nsresult rv; nsAutoString newNameStr(newName); if (newNameStr.FindChar(m_hierarchyDelimiter, 0) != kNotFound) { nsCOMPtr docShell; if (msgWindow) msgWindow->GetRootDocShell(getter_AddRefs(docShell)); if (docShell) { nsCOMPtr bundle; rv = IMAPGetStringBundle(getter_AddRefs(bundle)); if (NS_SUCCEEDED(rv) && bundle) { AutoTArray formatStrings; formatStrings.AppendElement()->Append(m_hierarchyDelimiter); nsString alertString; rv = bundle->FormatStringFromName("imapSpecialChar2", formatStrings, alertString); nsCOMPtr dialog(do_GetInterface(docShell)); // setting up the dialog title nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsString dialogTitle; nsString accountName; rv = server->GetPrettyName(accountName); NS_ENSURE_SUCCESS(rv, rv); AutoTArray titleParams = {accountName}; rv = bundle->FormatStringFromName("imapAlertDialogTitle", titleParams, dialogTitle); if (dialog && !alertString.IsEmpty()) dialog->Alert(dialogTitle.get(), alertString.get()); } } return NS_ERROR_FAILURE; } nsCOMPtr incomingImapServer; GetImapIncomingServer(getter_AddRefs(incomingImapServer)); if (incomingImapServer) RecursiveCloseActiveConnections(incomingImapServer); nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->RenameLeaf(this, newName, this, msgWindow); } NS_IMETHODIMP nsImapMailFolder::RecursiveCloseActiveConnections( nsIImapIncomingServer* incomingImapServer) { NS_ENSURE_ARG(incomingImapServer); nsCOMPtr folder; int32_t count = mSubFolders.Count(); for (int32_t i = 0; i < count; i++) { folder = do_QueryInterface(mSubFolders[i]); if (folder) folder->RecursiveCloseActiveConnections(incomingImapServer); incomingImapServer->CloseConnectionForFolder(mSubFolders[i]); } return NS_OK; } // this is called *after* we've done the rename on the server. NS_IMETHODIMP nsImapMailFolder::PrepareToRename() { nsCOMPtr folder; int32_t count = mSubFolders.Count(); for (int32_t i = 0; i < count; i++) { folder = do_QueryInterface(mSubFolders[i]); if (folder) folder->PrepareToRename(); } SetOnlineName(EmptyCString()); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::RenameLocal(const nsACString& newName, nsIMsgFolder* parent) { nsAutoCString leafname(newName); nsAutoCString parentName; // newName always in the canonical form "greatparent/parentname/leafname" int32_t leafpos = leafname.RFindChar('/'); if (leafpos > 0) leafname.Cut(0, leafpos + 1); m_msgParser = nullptr; PrepareToRename(); CloseAndBackupFolderDB(leafname); nsresult rv = NS_OK; nsCOMPtr oldPathFile; rv = GetFilePath(getter_AddRefs(oldPathFile)); if (NS_FAILED(rv)) return rv; nsCOMPtr parentPathFile; rv = parent->GetFilePath(getter_AddRefs(parentPathFile)); NS_ENSURE_SUCCESS(rv, rv); bool isDirectory = false; parentPathFile->IsDirectory(&isDirectory); if (!isDirectory) AddDirectorySeparator(parentPathFile); nsCOMPtr dirFile; int32_t count = mSubFolders.Count(); if (count > 0) { rv = CreateDirectoryForFolder(getter_AddRefs(dirFile)); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr oldSummaryFile; rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString newNameStr; oldSummaryFile->Remove(false); if (count > 0) { newNameStr = leafname; NS_MsgHashIfNecessary(newNameStr); newNameStr.AppendLiteral(FOLDER_SUFFIX8); nsAutoCString leafName; dirFile->GetNativeLeafName(leafName); if (!leafName.Equals(newNameStr)) return dirFile->MoveToNative( nullptr, newNameStr); // in case of rename operation leaf names will differ parentPathFile->AppendNative( newNameStr); // only for move we need to progress further in case the // parent differs bool isDirectory = false; parentPathFile->IsDirectory(&isDirectory); if (!isDirectory) { rv = parentPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700); NS_ENSURE_SUCCESS(rv, rv); } else { NS_ERROR("Directory already exists."); } rv = RecursiveCopy(dirFile, parentPathFile); NS_ENSURE_SUCCESS(rv, rv); dirFile->Remove(true); // moving folders } return rv; } NS_IMETHODIMP nsImapMailFolder::GetPrettyName(nsAString& prettyName) { return GetName(prettyName); } NS_IMETHODIMP nsImapMailFolder::UpdateSummaryTotals(bool force) { // bug 72871 inserted the mIsServer check for IMAP return mIsServer ? NS_OK : nsMsgDBFolder::UpdateSummaryTotals(force); } NS_IMETHODIMP nsImapMailFolder::GetDeletable(bool* deletable) { NS_ENSURE_ARG_POINTER(deletable); bool isServer; GetIsServer(&isServer); *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse)); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t* size) { NS_ENSURE_ARG_POINTER(size); bool isServer = false; nsresult rv = GetIsServer(&isServer); // If this is the rootFolder, return 0 as a safe value. if (NS_FAILED(rv) || isServer) mFolderSize = 0; *size = mFolderSize; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetCanCreateSubfolders(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = !(mFlags & (nsMsgFolderFlags::ImapNoinferiors | nsMsgFolderFlags::Virtual)); bool isServer = false; GetIsServer(&isServer); if (!isServer) { nsCOMPtr imapServer; nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer)); bool dualUseFolders = true; if (NS_SUCCEEDED(rv) && imapServer) imapServer->GetDualUseFolders(&dualUseFolders); if (!dualUseFolders && *aResult) *aResult = (mFlags & nsMsgFolderFlags::ImapNoselect); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetCanSubscribe(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = false; bool isImapServer = false; nsresult rv = GetIsServer(&isImapServer); if (NS_FAILED(rv)) return rv; // you can only subscribe to imap servers, not imap folders *aResult = isImapServer; return NS_OK; } nsresult nsImapMailFolder::GetServerKey(nsACString& serverKey) { // look for matching imap folders, then pop folders nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); if (NS_SUCCEEDED(rv)) rv = server->GetKey(serverKey); return rv; } NS_IMETHODIMP nsImapMailFolder::GetImapIncomingServer( nsIImapIncomingServer** aImapIncomingServer) { NS_ENSURE_ARG(aImapIncomingServer); nsCOMPtr server; if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) { nsCOMPtr incomingServer = do_QueryInterface(server); NS_ENSURE_TRUE(incomingServer, NS_ERROR_NO_INTERFACE); incomingServer.forget(aImapIncomingServer); return NS_OK; } return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsImapMailFolder::AddMessageDispositionState( nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) { nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag); // set the mark message answered flag on the server for this message... if (aMessage) { nsMsgKey msgKey; aMessage->GetMessageKey(&msgKey); if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied) StoreImapFlags(kImapMsgAnsweredFlag, true, {msgKey}, nullptr); else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded) StoreImapFlags(kImapMsgForwardedFlag, true, {msgKey}, nullptr); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::MarkMessagesRead( const nsTArray>& messages, bool markRead) { // tell the folder to do it, which will mark them read in the db. nsresult rv = nsMsgDBFolder::MarkMessagesRead(messages, markRead); if (NS_SUCCEEDED(rv)) { nsAutoCString messageIds; nsTArray keysToMarkRead; rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkRead); NS_ENSURE_SUCCESS(rv, rv); StoreImapFlags(kImapMsgSeenFlag, markRead, keysToMarkRead, nullptr); rv = GetDatabase(); if (NS_SUCCEEDED(rv)) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } return rv; } NS_IMETHODIMP nsImapMailFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) { nsresult rv = GetDatabase(); if (NS_SUCCEEDED(rv)) { nsTArray thoseMarked; EnableNotifications(allMessageCountNotifications, false); rv = mDatabase->MarkAllRead(thoseMarked); EnableNotifications(allMessageCountNotifications, true); if (NS_SUCCEEDED(rv) && thoseMarked.Length() > 0) { rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked, nullptr); mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // Setup a undo-state if (aMsgWindow) rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(), thoseMarked.Length()); } } return rv; } NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread* thread) { nsresult rv = GetDatabase(); if (NS_SUCCEEDED(rv)) { nsTArray keys; rv = mDatabase->MarkThreadRead(thread, nullptr, keys); if (NS_SUCCEEDED(rv) && keys.Length() > 0) { rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, nullptr); mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } } return rv; } NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem( nsIMsgFolderCacheElement* element) { nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element); int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown; nsCString onlineName; element->GetCachedUInt32("boxFlags", (uint32_t*)&m_boxFlags); if (NS_SUCCEEDED(element->GetCachedInt32("hierDelim", &hierarchyDelimiter)) && hierarchyDelimiter != kOnlineHierarchySeparatorUnknown) m_hierarchyDelimiter = (char)hierarchyDelimiter; rv = element->GetCachedString("onlineName", onlineName); if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty()) m_onlineFolderName.Assign(onlineName); m_aclFlags = kAclInvalid; // init to invalid value. element->GetCachedUInt32("aclFlags", &m_aclFlags); element->GetCachedInt32("serverTotal", &m_numServerTotalMessages); element->GetCachedInt32("serverUnseen", &m_numServerUnseenMessages); element->GetCachedInt32("serverRecent", &m_numServerRecentMessages); element->GetCachedInt32("nextUID", &m_nextUID); int32_t lastSyncTimeInSec; if (NS_FAILED(element->GetCachedInt32("lastSyncTimeInSec", (int32_t*)&lastSyncTimeInSec))) lastSyncTimeInSec = 0U; // make sure that auto-sync state object is created InitAutoSyncState(); m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec); return rv; } NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem( nsIMsgFolderCacheElement* element) { nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element); element->SetCachedUInt32("boxFlags", (uint32_t)m_boxFlags); element->SetCachedInt32("hierDelim", (int32_t)m_hierarchyDelimiter); element->SetCachedString("onlineName", m_onlineFolderName); element->SetCachedUInt32("aclFlags", m_aclFlags); element->SetCachedInt32("serverTotal", m_numServerTotalMessages); element->SetCachedInt32("serverUnseen", m_numServerUnseenMessages); element->SetCachedInt32("serverRecent", m_numServerRecentMessages); if (m_nextUID != (int32_t)nsMsgKey_None) element->SetCachedInt32("nextUID", m_nextUID); // store folder's last sync time if (m_autoSyncStateObj) { PRTime lastSyncTime; m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime); // store in sec element->SetCachedInt32("lastSyncTimeInSec", (int32_t)(lastSyncTime / PR_USEC_PER_SEC)); } return rv; } NS_IMETHODIMP nsImapMailFolder::MarkMessagesFlagged( const nsTArray>& messages, bool markFlagged) { nsresult rv; // tell the folder to do it, which will mark them read in the db. rv = nsMsgDBFolder::MarkMessagesFlagged(messages, markFlagged); if (NS_SUCCEEDED(rv)) { nsAutoCString messageIds; nsTArray keysToMarkFlagged; rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkFlagged); if (NS_FAILED(rv)) return rv; rv = StoreImapFlags(kImapMsgFlaggedFlag, markFlagged, keysToMarkFlagged, nullptr); NS_ENSURE_SUCCESS(rv, rv); rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } return rv; } NS_IMETHODIMP nsImapMailFolder::SetOnlineName( const nsACString& aOnlineFolderName) { nsresult rv; nsCOMPtr db; nsCOMPtr folderInfo; rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); // do this after GetDBFolderInfoAndDB, because it crunches m_onlineFolderName // (not sure why) m_onlineFolderName = aOnlineFolderName; if (NS_SUCCEEDED(rv) && folderInfo) { nsAutoString onlineName; CopyUTF8toUTF16(aOnlineFolderName, onlineName); rv = folderInfo->SetProperty("onlineName", onlineName); rv = folderInfo->SetMailboxName(onlineName); // so, when are we going to commit this? Definitely not every time! // We could check if the online name has changed. db->Commit(nsMsgDBCommitType::kLargeCommit); } folderInfo = nullptr; return rv; } NS_IMETHODIMP nsImapMailFolder::GetOnlineName(nsACString& aOnlineFolderName) { ReadDBFolderInfo(false); // update cache first. aOnlineFolderName = m_onlineFolderName; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo, nsIMsgDatabase** db) { NS_ENSURE_ARG_POINTER(folderInfo); NS_ENSURE_ARG_POINTER(db); nsresult rv = GetDatabase(); if (NS_FAILED(rv)) return rv; NS_ADDREF(*db = mDatabase); rv = (*db)->GetDBFolderInfo(folderInfo); if (NS_FAILED(rv)) return rv; // GetDBFolderInfo can't return NS_OK if !folderInfo nsCString onlineName; rv = (*folderInfo)->GetCharProperty("onlineName", onlineName); if (NS_FAILED(rv)) return rv; if (!onlineName.IsEmpty()) m_onlineFolderName.Assign(onlineName); else { nsAutoString autoOnlineName; (*folderInfo)->GetMailboxName(autoOnlineName); if (autoOnlineName.IsEmpty()) { nsCString uri; rv = GetURI(uri); NS_ENSURE_SUCCESS(rv, rv); nsCString hostname; rv = GetHostname(hostname); NS_ENSURE_SUCCESS(rv, rv); nsCString onlineCName; rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(), getter_Copies(onlineCName)); // Note: check for unknown separator '^' only became needed // with UTF8=ACCEPT modification and haven't found why. Online name // contained the '^' delimiter and gmail said "NO" when folder under // [Gmail] is created and selected. if ((m_hierarchyDelimiter != '/') && (m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)) onlineCName.ReplaceChar('/', m_hierarchyDelimiter); // XXX: What if online name contains slashes? Breaks? m_onlineFolderName.Assign(onlineCName); CopyUTF8toUTF16(onlineCName, autoOnlineName); } (*folderInfo)->SetProperty("onlineName", autoOnlineName); } return rv; } /* static */ nsresult nsImapMailFolder::BuildIdsAndKeyArray( const nsTArray>& messages, nsCString& msgIds, nsTArray& keyArray) { keyArray.Clear(); keyArray.SetCapacity(messages.Length()); // build up message keys. for (auto msgDBHdr : messages) { nsMsgKey key; nsresult rv = msgDBHdr->GetMessageKey(&key); if (NS_SUCCEEDED(rv)) keyArray.AppendElement(key); } return AllocateUidStringFromKeys(keyArray, msgIds); } /* static */ nsresult nsImapMailFolder::AllocateUidStringFromKeys( const nsTArray& keys, nsCString& msgIds) { if (keys.IsEmpty()) return NS_ERROR_INVALID_ARG; nsresult rv = NS_OK; uint32_t startSequence; startSequence = keys[0]; uint32_t curSequenceEnd = startSequence; uint32_t total = keys.Length(); // sort keys and then generate ranges instead of singletons! nsTArray sorted(keys.Clone()); sorted.Sort(); for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) { uint32_t curKey = sorted[keyIndex]; uint32_t nextKey = (keyIndex + 1 < total) ? sorted[keyIndex + 1] : 0xFFFFFFFF; bool lastKey = (nextKey == 0xFFFFFFFF); if (lastKey) curSequenceEnd = curKey; if (nextKey == (uint32_t)curSequenceEnd + 1 && !lastKey) { curSequenceEnd = nextKey; continue; } if (curSequenceEnd > startSequence) { AppendUid(msgIds, startSequence); msgIds += ':'; AppendUid(msgIds, curSequenceEnd); if (!lastKey) msgIds += ','; startSequence = nextKey; curSequenceEnd = startSequence; } else { startSequence = nextKey; curSequenceEnd = startSequence; AppendUid(msgIds, sorted[keyIndex]); if (!lastKey) msgIds += ','; } } return rv; } nsresult nsImapMailFolder::MarkMessagesImapDeleted(nsTArray* keyArray, bool deleted, nsIMsgDatabase* db) { for (uint32_t kindex = 0; kindex < keyArray->Length(); kindex++) { nsMsgKey key = keyArray->ElementAt(kindex); db->MarkImapDeleted(key, deleted, nullptr); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::DeleteMessages( nsTArray> const& msgHeaders, nsIMsgWindow* msgWindow, bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener, bool allowUndo) { // *** jt - assuming delete is move to the trash folder for now nsAutoCString uri; bool deleteImmediatelyNoTrash = false; nsAutoCString messageIds; nsTArray srcKeyArray; bool deleteMsgs = true; // used for toggling delete status - default is true nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash; imapMessageFlagsType messageFlags = kImapMsgDeletedFlag; nsCOMPtr imapServer; nsresult rv = GetFlag(nsMsgFolderFlags::Trash, &deleteImmediatelyNoTrash); rv = GetImapIncomingServer(getter_AddRefs(imapServer)); if (NS_SUCCEEDED(rv) && imapServer) { imapServer->GetDeleteModel(&deleteModel); if (deleteModel != nsMsgImapDeleteModels::MoveToTrash || deleteStorage) deleteImmediatelyNoTrash = true; // if we're deleting a message, we should pseudo-interrupt the msg // load of the current message. bool interrupted = false; imapServer->PseudoInterruptMsgLoad(this, msgWindow, &interrupted); } rv = BuildIdsAndKeyArray(msgHeaders, messageIds, srcKeyArray); if (NS_FAILED(rv)) return rv; nsCOMPtr rootFolder; nsCOMPtr trashFolder; if (!deleteImmediatelyNoTrash) { rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && rootFolder) { rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, getter_AddRefs(trashFolder)); NS_ASSERTION(trashFolder, "couldn't find trash"); // if we can't find the trash, we'll just have to do an imap delete and // pretend this is the trash if (!trashFolder) deleteImmediatelyNoTrash = true; } } if ((NS_SUCCEEDED(rv) && deleteImmediatelyNoTrash) || deleteModel == nsMsgImapDeleteModels::IMAPDelete) { if (allowUndo) { // need to take care of these two delete models RefPtr undoMsgTxn = new nsImapMoveCopyMsgTxn; if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(this, &srcKeyArray, messageIds.get(), nullptr, true, isMove))) return NS_ERROR_OUT_OF_MEMORY; undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); // we're adding this undo action before the delete is successful. This is // evil, but 4.5 did it as well. nsCOMPtr txnMgr; if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) txnMgr->DoTransaction(undoMsgTxn); } if (deleteModel == nsMsgImapDeleteModels::IMAPDelete && !deleteStorage) { deleteMsgs = false; for (nsIMsgDBHdr* msgHdr : msgHeaders) { if (!msgHdr) { continue; } uint32_t flags; msgHdr->GetFlags(&flags); if (!(flags & nsMsgMessageFlags::IMAPDeleted)) { deleteMsgs = true; break; } } } // if copy service listener is also a url listener, pass that // url listener into StoreImapFlags. nsCOMPtr urlListener = do_QueryInterface(listener); if (deleteMsgs) messageFlags |= kImapMsgSeenFlag; rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray, urlListener); if (NS_SUCCEEDED(rv)) { if (mDatabase) { nsCOMPtr database(mDatabase); if (deleteModel == nsMsgImapDeleteModels::IMAPDelete) MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database); else { EnableNotifications(allMessageCountNotifications, false); //"remove it immediately" model // Notify if this is an actual delete. if (!isMove) { nsCOMPtr notifier(do_GetService( "@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) notifier->NotifyMsgsDeleted(msgHeaders); } DeleteStoreMessages(msgHeaders); database->DeleteMessages(srcKeyArray, nullptr); EnableNotifications(allMessageCountNotifications, true); } if (listener) { listener->OnStartCopy(); listener->OnStopCopy(NS_OK); } NotifyFolderEvent(kDeleteOrMoveMsgCompleted); } } return rv; } // have to move the messages to the trash if (trashFolder) { nsCOMPtr srcFolder; nsCOMPtr srcSupport; rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder)); nsCOMPtr copyService = do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = copyService->CopyMessages(srcFolder, msgHeaders, trashFolder, true, listener, msgWindow, allowUndo); } return rv; } // check if folder is the trash, or a descendent of the trash // so we can tell if the folders we're deleting from it should // be *really* deleted. bool nsImapMailFolder::TrashOrDescendentOfTrash(nsIMsgFolder* folder) { NS_ENSURE_TRUE(folder, false); nsCOMPtr parent; nsCOMPtr curFolder = folder; nsresult rv; uint32_t flags = 0; do { rv = curFolder->GetFlags(&flags); if (NS_FAILED(rv)) return false; if (flags & nsMsgFolderFlags::Trash) return true; curFolder->GetParent(getter_AddRefs(parent)); if (!parent) return false; curFolder = parent; } while (NS_SUCCEEDED(rv) && curFolder); return false; } NS_IMETHODIMP nsImapMailFolder::DeleteSelf(nsIMsgWindow* msgWindow) { nsCOMPtr trashFolder; nsresult rv; uint32_t folderFlags; // No IMAP shenanigans required for virtual folders. GetFlags(&folderFlags); if (folderFlags & nsMsgFolderFlags::Virtual) { return nsMsgDBFolder::DeleteSelf(nullptr); } // "this" is the folder we're deleting from bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash(); bool confirmDeletion = true; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); if (!deleteNoTrash) { rv = GetTrashFolder(getter_AddRefs(trashFolder)); // If we can't find the trash folder and we are supposed to move it to the // trash return failure. if (NS_FAILED(rv) || !trashFolder) return NS_ERROR_FAILURE; bool canHaveSubFoldersOfTrash = true; trashFolder->GetCanCreateSubfolders(&canHaveSubFoldersOfTrash); if (canHaveSubFoldersOfTrash) // UW server doesn't set NOINFERIORS - check // dual use pref { nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); bool serverSupportsDualUseFolders; imapServer->GetDualUseFolders(&serverSupportsDualUseFolders); if (!serverSupportsDualUseFolders) canHaveSubFoldersOfTrash = false; } if (!canHaveSubFoldersOfTrash) deleteNoTrash = true; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); prefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion); } // If we are deleting folder immediately, ask user for confirmation. bool confirmed = false; if (confirmDeletion || deleteNoTrash) { nsCOMPtr bundle; rv = IMAPGetStringBundle(getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString folderName; rv = GetName(folderName); NS_ENSURE_SUCCESS(rv, rv); AutoTArray formatStrings = {folderName}; nsAutoString deleteFolderDialogTitle; rv = bundle->GetStringFromName("imapDeleteFolderDialogTitle", deleteFolderDialogTitle); NS_ENSURE_SUCCESS(rv, rv); nsAutoString deleteFolderButtonLabel; rv = bundle->GetStringFromName("imapDeleteFolderButtonLabel", deleteFolderButtonLabel); NS_ENSURE_SUCCESS(rv, rv); nsAutoString confirmationStr; rv = bundle->FormatStringFromName( (deleteNoTrash) ? "imapDeleteNoTrash" : "imapMoveFolderToTrash", formatStrings, confirmationStr); NS_ENSURE_SUCCESS(rv, rv); if (!msgWindow) return NS_ERROR_NULL_POINTER; nsCOMPtr docShell; msgWindow->GetRootDocShell(getter_AddRefs(docShell)); nsCOMPtr dialog; if (docShell) dialog = do_GetInterface(docShell); if (dialog) { int32_t buttonPressed = 0; // Default the dialog to "cancel". const uint32_t buttonFlags = (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1); bool dummyValue = false; rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), confirmationStr.get(), buttonFlags, deleteFolderButtonLabel.get(), nullptr, nullptr, nullptr, &dummyValue, &buttonPressed); NS_ENSURE_SUCCESS(rv, rv); confirmed = !buttonPressed; // "ok" is in position 0 } } else { confirmed = true; } if (confirmed) { if (deleteNoTrash) { rv = imapService->DeleteFolder(this, this, msgWindow); nsMsgDBFolder::DeleteSelf(msgWindow); } else { bool match = false; rv = MatchOrChangeFilterDestination(nullptr, false, &match); if (match) { bool confirm = false; ConfirmFolderDeletionForFilter(msgWindow, &confirm); if (!confirm) return NS_OK; } rv = imapService->MoveFolder(this, trashFolder, this, msgWindow); } } return rv; } // FIXME: helper function to know whether we should check all IMAP folders // for new mail; this is necessary because of a legacy hidden preference // mail.check_all_imap_folders_for_new (now replaced by per-server preference // mail.server.%serverkey%.check_all_folders_for_new), still present in some // profiles. /*static*/ bool nsImapMailFolder::ShouldCheckAllFolders( nsIImapIncomingServer* imapServer) { // Check legacy global preference to see if we should check all folders for // new messages, or just the inbox and marked ones. bool checkAllFolders = false; nsresult rv; nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, false); // This pref might not exist, which is OK. (void)prefBranch->GetBoolPref("mail.check_all_imap_folders_for_new", &checkAllFolders); if (checkAllFolders) return true; // If the legacy preference doesn't exist or has its default value (False), // the true preference is read. imapServer->GetCheckAllFoldersForNew(&checkAllFolders); return checkAllFolders; } // Called by Biff, or when user presses GetMsg button. NS_IMETHODIMP nsImapMailFolder::GetNewMessages(nsIMsgWindow* aWindow, nsIUrlListener* aListener) { nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && rootFolder) { nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); bool performingBiff = false; nsCOMPtr incomingServer = do_QueryInterface(imapServer, &rv); NS_ENSURE_SUCCESS(rv, rv); incomingServer->GetPerformingBiff(&performingBiff); m_urlListener = aListener; // See if we should check all folders for new messages, or just the inbox // and marked ones bool checkAllFolders = ShouldCheckAllFolders(imapServer); // Get new messages for inbox nsCOMPtr inbox; rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inbox)); if (inbox) { nsCOMPtr imapFolder = do_QueryInterface(inbox, &rv); NS_ENSURE_SUCCESS(rv, rv); imapFolder->SetPerformingBiff(performingBiff); inbox->SetGettingNewMessages(true); rv = inbox->UpdateFolder(aWindow); } // Get new messages for other folders if marked, or all of them if the pref // is set rv = imapServer->GetNewMessagesForNonInboxFolders( rootFolder, aWindow, checkAllFolders, performingBiff); } return rv; } NS_IMETHODIMP nsImapMailFolder::Shutdown(bool shutdownChildren) { m_filterList = nullptr; m_initialized = false; // mPath is used to decide if folder pathname needs to be reconstructed in // GetPath(). mPath = nullptr; m_moveCoalescer = nullptr; m_msgParser = nullptr; if (m_playbackTimer) { m_playbackTimer->Cancel(); m_playbackTimer = nullptr; } m_pendingOfflineMoves.Clear(); return nsMsgDBFolder::Shutdown(shutdownChildren); } nsresult nsImapMailFolder::GetBodysToDownload( nsTArray* keysOfMessagesToDownload) { NS_ENSURE_ARG(keysOfMessagesToDownload); NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE); nsCOMPtr enumerator; nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator)); if (NS_SUCCEEDED(rv) && enumerator) { bool hasMore; nsCOMPtr header; nsMsgKey msgKey; while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore) { rv = enumerator->GetNext(getter_AddRefs(header)); NS_ENSURE_SUCCESS(rv, rv); bool shouldStoreMsgOffline = false; header->GetMessageKey(&msgKey); // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we // want if (m_downloadingFolderForOfflineUse) MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline); else ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline); if (shouldStoreMsgOffline) keysOfMessagesToDownload->AppendElement(msgKey); } if (MOZ_LOG_TEST(gAutoSyncLog, mozilla::LogLevel::Debug) && header) { // Log this only if folder is not empty. uint32_t msgFlags = 0; header->GetFlags(&msgFlags); MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("%s: num keys to download=%zu, last key=%d, last msg flag=0x%x " "nsMsgMessageFlags::Offline=0x%x", __func__, keysOfMessagesToDownload->Length(), msgKey, msgFlags, nsMsgMessageFlags::Offline)); } } return rv; } NS_IMETHODIMP nsImapMailFolder::OnNewIdleMessages() { nsresult rv; nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); bool checkAllFolders = ShouldCheckAllFolders(imapServer); // only trigger biff if we're checking all new folders for new messages, or // this particular folder, but excluding trash,junk, sent, and no select // folders, by default. if ((checkAllFolders && !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk | nsMsgFolderFlags::SentMail | nsMsgFolderFlags::ImapNoselect))) || (mFlags & (nsMsgFolderFlags::CheckNew | nsMsgFolderFlags::Inbox))) SetPerformingBiff(true); return UpdateFolder(nullptr); } NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxInfo( nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec) { nsresult rv; ChangeNumPendingTotalMessages(-mNumPendingTotalMessages); ChangeNumPendingUnread(-mNumPendingUnreadMessages); m_numServerRecentMessages = 0; // clear this since we selected the folder. if (!mDatabase) GetDatabase(); bool folderSelected; rv = aSpec->GetFolderSelected(&folderSelected); NS_ENSURE_SUCCESS(rv, rv); nsTArray existingKeys; nsTArray keysToDelete; uint32_t numNewUnread; nsCOMPtr dbFolderInfo; int32_t imapUIDValidity = 0; if (mDatabase) { rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (NS_SUCCEEDED(rv) && dbFolderInfo) { dbFolderInfo->GetImapUidValidity(&imapUIDValidity); uint64_t mailboxHighestModSeq; aSpec->GetHighestModSeq(&mailboxHighestModSeq); MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug, ("UpdateImapMailboxInfo(): Store highest MODSEQ=%" PRIu64 " for folder=%s", mailboxHighestModSeq, m_onlineFolderName.get())); char intStrBuf[40]; PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", mailboxHighestModSeq); dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf)); } nsTArray keys; rv = mDatabase->ListAllKeys(keys); NS_ENSURE_SUCCESS(rv, rv); existingKeys.AppendElements(keys); nsCOMPtr opsDb = do_QueryInterface(mDatabase, &rv); NS_ENSURE_SUCCESS(rv, rv); opsDb->ListAllOfflineDeletes(existingKeys); } int32_t folderValidity; aSpec->GetFolder_UIDVALIDITY(&folderValidity); nsCOMPtr flagState; aSpec->GetFlagState(getter_AddRefs(flagState)); // remember what the supported user flags are. uint32_t supportedUserFlags; aSpec->GetSupportedUserFlags(&supportedUserFlags); SetSupportedUserFlags(supportedUserFlags); m_uidValidity = folderValidity; if (imapUIDValidity != folderValidity) { NS_ASSERTION(imapUIDValidity == kUidUnknown, "uid validity seems to have changed, blowing away db"); nsCOMPtr pathFile; rv = GetFilePath(getter_AddRefs(pathFile)); if (NS_FAILED(rv)) return rv; nsCOMPtr msgDBService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr transferInfo; if (dbFolderInfo) dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo)); // A backup message database might have been created earlier, for example // if the user requested a reindex. We'll use the earlier one if we can, // otherwise we'll try to backup at this point. nsresult rvbackup = OpenBackupMsgDatabase(); if (mDatabase) { dbFolderInfo = nullptr; if (NS_FAILED(rvbackup)) { CloseAndBackupFolderDB(EmptyCString()); if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) { mBackupDatabase->RemoveListener(this); mBackupDatabase = nullptr; } } else mDatabase->ForceClosed(); } mDatabase = nullptr; nsCOMPtr summaryFile; rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile)); // Remove summary file. if (NS_SUCCEEDED(rv) && summaryFile) summaryFile->Remove(false); // Create a new summary file, update the folder message counts, and // Close the summary file db. rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); if (NS_FAILED(rv) && mDatabase) { mDatabase->ForceClosed(); mDatabase = nullptr; } else if (NS_SUCCEEDED(rv) && mDatabase) { if (transferInfo) SetDBTransferInfo(transferInfo); SummaryChanged(); if (mDatabase) { if (mAddListener) mDatabase->AddListener(this); rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); } } // store the new UIDVALIDITY value if (NS_SUCCEEDED(rv) && dbFolderInfo) { dbFolderInfo->SetImapUidValidity(folderValidity); // need to forget highest mod seq when uid validity rolls. MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug, ("UpdateImapMailboxInfo(): UIDVALIDITY changed, reset highest " "MODSEQ and UID for folder=%s", m_onlineFolderName.get())); dbFolderInfo->SetCharProperty(kModSeqPropertyName, EmptyCString()); dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, 0); } // delete all my msgs, the keys are bogus now // add every message in this folder existingKeys.Clear(); // keysToDelete.CopyArray(&existingKeys); if (flagState) { nsTArray no_existingKeys; FindKeysToAdd(no_existingKeys, m_keysToFetch, numNewUnread, flagState); } if (NS_FAILED(rv)) pathFile->Remove(false); } else if (!flagState /*&& !NET_IsOffline() */) // if there are no messages // on the server keysToDelete = existingKeys.Clone(); else /* if ( !NET_IsOffline()) */ { uint32_t boxFlags; aSpec->GetBox_flags(&boxFlags); // FindKeysToDelete and FindKeysToAdd require sorted lists existingKeys.Sort(); FindKeysToDelete(existingKeys, keysToDelete, flagState, boxFlags); // if this is the result of an expunge then don't grab headers if (!(boxFlags & kJustExpunged)) FindKeysToAdd(existingKeys, m_keysToFetch, numNewUnread, flagState); } m_totalKeysToFetch = m_keysToFetch.Length(); if (!keysToDelete.IsEmpty() && mDatabase) { nsTArray> hdrsToDelete; MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete); // Notify nsIMsgFolderListeners of a mass delete, but only if we actually // have headers if (!hdrsToDelete.IsEmpty()) { nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) notifier->NotifyMsgsDeleted(hdrsToDelete); } DeleteStoreMessages(hdrsToDelete); EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false); mDatabase->DeleteMessages(keysToDelete, nullptr); EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true); } int32_t numUnreadFromServer; aSpec->GetNumUnseenMessages(&numUnreadFromServer); bool partialUIDFetch; flagState->GetPartialUIDFetch(&partialUIDFetch); // For partial UID fetches (i.e., occurs when CONDSTORE in effect), we can // only trust the numUnread from the server. However, even that will only be // correct if a recent imap STATUS occurred as indicated by // numUnreadFromServer greater than -1. if (partialUIDFetch) numNewUnread = numUnreadFromServer; // If we are performing biff for this folder, tell the // stand-alone biff about the new high water mark if (m_performingBiff && numNewUnread && static_cast(numNewUnread) != -1) { // We must ensure that the server knows that we are performing biff. // Otherwise the stand-alone biff won't fire. nsCOMPtr server; if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) server->SetPerformingBiff(true); SetNumNewMessages(numNewUnread); } SyncFlags(flagState); if (mDatabase && numUnreadFromServer > -1 && (int32_t)(mNumUnreadMessages + m_keysToFetch.Length()) > numUnreadFromServer) mDatabase->SyncCounts(); if (!m_keysToFetch.IsEmpty() && aProtocol) PrepareToAddHeadersToMailDB(aProtocol); else { bool gettingNewMessages; GetGettingNewMessages(&gettingNewMessages); if (gettingNewMessages) ProgressStatusString(aProtocol, "imapNoNewMessages", nullptr); SetPerformingBiff(false); } aSpec->GetNumMessages(&m_numServerTotalMessages); if (numUnreadFromServer > -1) m_numServerUnseenMessages = numUnreadFromServer; aSpec->GetNumRecentMessages(&m_numServerRecentMessages); // some servers don't return UIDNEXT on SELECT - don't crunch // existing values in that case. int32_t nextUID; aSpec->GetNextUID(&nextUID); if (nextUID != (int32_t)nsMsgKey_None) m_nextUID = nextUID; return rv; } /** * Called after successful imap STATUS response occurs. Have valid unseen value * if folderstatus URL produced an imap STATUS. If a NOOP occurs instead (doing * folderstatus from a connection SELECTed on the same folder) there is no * UNSEEN returned by NOOP. */ NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxStatus( nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec) { NS_ENSURE_ARG_POINTER(aSpec); int32_t numUnread, numTotal; aSpec->GetNumUnseenMessages(&numUnread); aSpec->GetNumMessages(&numTotal); aSpec->GetNumRecentMessages(&m_numServerRecentMessages); int32_t prevNextUID = m_nextUID; aSpec->GetNextUID(&m_nextUID); bool summaryChanged = false; // If m_numServerUnseenMessages is 0, it means // this is the first time we've done a Status. // In that case, we count all the previous pending unread messages we know // about as unread messages. We may want to do similar things with total // messages, but the total messages include deleted messages if the folder // hasn't been expunged. int32_t previousUnreadMessages = (m_numServerUnseenMessages) ? m_numServerUnseenMessages : mNumPendingUnreadMessages + mNumUnreadMessages; if (numUnread == -1) { // A noop occurred so don't know server's UNSEEN number, keep using the // previously known unread count. MOZ_LOG(IMAP, mozilla::LogLevel::Debug, ("%s: folder=%s, unread was -1, set numUnread to previousUnread=%d", __func__, m_onlineFolderName.get(), previousUnreadMessages)); numUnread = previousUnreadMessages; } if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID) { int32_t unreadDelta = numUnread - (mNumPendingUnreadMessages + mNumUnreadMessages); if (numUnread - previousUnreadMessages != unreadDelta) NS_WARNING("unread count should match server count"); ChangeNumPendingUnread(unreadDelta); if (unreadDelta > 0 && !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) { SetHasNewMessages(true); SetNumNewMessages(unreadDelta); SetBiffState(nsMsgBiffState_NewMail); } summaryChanged = true; } SetPerformingBiff(false); if (m_numServerUnseenMessages != numUnread || m_numServerTotalMessages != numTotal) { if (numUnread > m_numServerUnseenMessages || m_numServerTotalMessages > numTotal) NotifyHasPendingMsgs(); summaryChanged = true; m_numServerUnseenMessages = numUnread; m_numServerTotalMessages = numTotal; } if (summaryChanged) SummaryChanged(); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::ParseMsgHdrs( nsIImapProtocol* aProtocol, nsIImapHeaderXferInfo* aHdrXferInfo) { NS_ENSURE_ARG_POINTER(aHdrXferInfo); int32_t numHdrs; nsCOMPtr headerInfo; nsCOMPtr aImapUrl; nsImapAction imapAction = nsIImapUrl::nsImapTest; // unused value. if (!mDatabase) GetDatabase(); nsresult rv = aHdrXferInfo->GetNumHeaders(&numHdrs); if (aProtocol) { (void)aProtocol->GetRunningImapURL(getter_AddRefs(aImapUrl)); if (aImapUrl) aImapUrl->GetImapAction(&imapAction); } for (uint32_t i = 0; NS_SUCCEEDED(rv) && (int32_t)i < numHdrs; i++) { rv = aHdrXferInfo->GetHeader(i, getter_AddRefs(headerInfo)); NS_ENSURE_SUCCESS(rv, rv); if (!headerInfo) break; int32_t msgSize; nsMsgKey msgKey; bool containsKey; nsCString msgHdrs; headerInfo->GetMsgSize(&msgSize); headerInfo->GetMsgUid(&msgKey); if (msgKey == nsMsgKey_None) // not a valid uid. continue; if (imapAction == nsIImapUrl::nsImapMsgPreview) { nsCOMPtr msgHdr; headerInfo->GetMsgHdrs(msgHdrs); // create an input stream based on the hdr string. nsCOMPtr inputStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); NS_ENSURE_SUCCESS(rv, rv); inputStream->ShareData(msgHdrs.get(), msgHdrs.Length()); GetMessageHeader(msgKey, getter_AddRefs(msgHdr)); if (msgHdr) { GetMsgPreviewTextFromStream(msgHdr, inputStream); } continue; } if (mDatabase && NS_SUCCEEDED(mDatabase->ContainsKey(msgKey, &containsKey)) && containsKey) { NS_ERROR("downloading hdrs for hdr we already have"); continue; } nsresult rv = SetupHeaderParseStream(msgSize, EmptyCString(), nullptr); NS_ENSURE_SUCCESS(rv, rv); headerInfo->GetMsgHdrs(msgHdrs); rv = ParseAdoptedHeaderLine(msgHdrs.get(), msgKey); NS_ENSURE_SUCCESS(rv, rv); rv = NormalEndHeaderParseStream(aProtocol, aImapUrl); } return rv; } nsresult nsImapMailFolder::SetupHeaderParseStream( uint32_t aSize, const nsACString& content_type, nsIMailboxSpec* boxSpec) { if (!mDatabase) GetDatabase(); m_nextMessageByteLength = aSize; if (!m_msgParser) { nsresult rv; m_msgParser = do_CreateInstance(kParseMailMsgStateCID, &rv); NS_ENSURE_SUCCESS(rv, rv); } else m_msgParser->Clear(); m_msgParser->SetMailDB(mDatabase); if (mBackupDatabase) m_msgParser->SetBackupMailDB(mBackupDatabase); return m_msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState); } nsresult nsImapMailFolder::ParseAdoptedHeaderLine(const char* aMessageLine, nsMsgKey aMsgKey) { // we can get blocks that contain more than one line, // but they never contain partial lines const char* str = aMessageLine; m_curMsgUid = aMsgKey; m_msgParser->SetNewKey(m_curMsgUid); // m_envelope_pos, for local folders, // is the msg key. Setting this will set the msg key for the new header. int32_t len = strlen(str); char* currentEOL = PL_strstr(str, MSG_LINEBREAK); const char* currentLine = str; while (currentLine < (str + len)) { if (currentEOL) { m_msgParser->ParseAFolderLine( currentLine, (currentEOL + MSG_LINEBREAK_LEN) - currentLine); currentLine = currentEOL + MSG_LINEBREAK_LEN; currentEOL = PL_strstr(currentLine, MSG_LINEBREAK); } else { m_msgParser->ParseAFolderLine(currentLine, PL_strlen(currentLine)); currentLine = str + len + 1; } } return NS_OK; } nsresult nsImapMailFolder::NormalEndHeaderParseStream( nsIImapProtocol* aProtocol, nsIImapUrl* imapUrl) { nsCOMPtr newMsgHdr; nsresult rv; NS_ENSURE_TRUE(m_msgParser, NS_ERROR_NULL_POINTER); nsMailboxParseState parseState; m_msgParser->GetState(&parseState); if (parseState == nsIMsgParseMailMsgState::ParseHeadersState) m_msgParser->ParseAFolderLine(CRLF, 2); rv = m_msgParser->GetNewMsgHdr(getter_AddRefs(newMsgHdr)); NS_ENSURE_SUCCESS(rv, rv); char* headers; int32_t headersSize; nsCOMPtr msgWindow; nsCOMPtr msgUrl; if (imapUrl) { msgUrl = do_QueryInterface(imapUrl, &rv); NS_ENSURE_SUCCESS(rv, rv); msgUrl->GetMsgWindow(getter_AddRefs(msgWindow)); } nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr imapServer = do_QueryInterface(server); rv = imapServer->GetIsGMailServer(&m_isGmailServer); NS_ENSURE_SUCCESS(rv, rv); newMsgHdr->SetMessageKey(m_curMsgUid); TweakHeaderFlags(aProtocol, newMsgHdr); uint32_t messageSize; if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize))) mFolderSize += messageSize; m_msgMovedByFilter = false; nsMsgKey highestUID = 0; nsCOMPtr dbFolderInfo; if (mDatabase) mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (dbFolderInfo) dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &highestUID); // If this is the inbox, try to apply filters. Otherwise, test the inherited // folder property "applyIncomingFilters" (which defaults to empty). If this // inherited property has the string value "true", then apply filters even // if this is not the Inbox folder. if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) { // Use highwater to determine whether to filter? bool filterOnHighwater = false; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefBranch) prefBranch->GetBoolPref("mail.imap.filter_on_new", &filterOnHighwater); uint32_t msgFlags; newMsgHdr->GetFlags(&msgFlags); // clang-format off bool doFilter = filterOnHighwater // Filter on largest UUID and not deleted. ? m_curMsgUid > highestUID && !(msgFlags & nsMsgMessageFlags::IMAPDeleted) // Filter on unread and not deleted. : !(msgFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted)); // clang-format on if (doFilter) MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) New message parsed, and filters will be run on it")); else MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) New message parsed, but filters will not be run on it")); if (doFilter) { int32_t duplicateAction = nsIMsgIncomingServer::keepDups; if (server) server->GetIncomingDuplicateAction(&duplicateAction); if ((duplicateAction != nsIMsgIncomingServer::keepDups) && mFlags & nsMsgFolderFlags::Inbox) { bool isDup; server->IsNewHdrDuplicate(newMsgHdr, &isDup); if (isDup) { // we want to do something similar to applying filter hits. // if a dup is marked read, it shouldn't trigger biff. // Same for deleting it or moving it to trash. switch (duplicateAction) { case nsIMsgIncomingServer::deleteDups: { uint32_t newFlags; newMsgHdr->OrFlags( nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted, &newFlags); StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true, {m_curMsgUid}, nullptr); m_msgMovedByFilter = true; } break; case nsIMsgIncomingServer::moveDupsToTrash: { nsCOMPtr trash; GetTrashFolder(getter_AddRefs(trash)); if (trash) { nsCString trashUri; trash->GetURI(trashUri); nsresult err = MoveIncorporatedMessage( newMsgHdr, mDatabase, trashUri, nullptr, msgWindow); if (NS_SUCCEEDED(err)) m_msgMovedByFilter = true; } } break; case nsIMsgIncomingServer::markDupsRead: { uint32_t newFlags; newMsgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); StoreImapFlags(kImapMsgSeenFlag, true, {m_curMsgUid}, nullptr); } break; } int32_t numNewMessages; GetNumNewMessages(false, &numNewMessages); SetNumNewMessages(numNewMessages - 1); } } rv = m_msgParser->GetAllHeaders(&headers, &headersSize); if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter && !m_filterListRequiresBody) { if (m_filterList) { GetMoveCoalescer(); // not sure why we're doing this here. MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("(Imap) ApplyFilterToHdr from " "nsImapMailFolder::NormalEndHeaderParseStream()")); m_filterList->ApplyFiltersToHdr( nsMsgFilterType::InboxRule, newMsgHdr, this, mDatabase, nsDependentCSubstring(headers, headersSize), this, msgWindow); NotifyFolderEvent(kFiltersApplied); } } } } // here we need to tweak flags from uid state.. if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages())) { nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); // Check if this header corresponds to a pseudo header // we have from doing a pseudo-offline move and then downloading // the real header from the server. In that case, we notify // db/folder listeners that the pseudo-header has become the new // header, i.e., the key has changed. nsCString newMessageId; newMsgHdr->GetMessageId(getter_Copies(newMessageId)); nsMsgKey pseudoKey = m_pseudoHdrs.MaybeGet(newMessageId).valueOr(nsMsgKey_None); if (notifier && pseudoKey != nsMsgKey_None) { notifier->NotifyMsgKeyChanged(pseudoKey, newMsgHdr); m_pseudoHdrs.Remove(newMessageId); } mDatabase->AddNewHdrToDB(newMsgHdr, true); if (notifier) notifier->NotifyMsgAdded(newMsgHdr); // mark the header as not yet reported classified OrProcessingFlags(m_curMsgUid, nsMsgProcessingFlags::NotReportedClassified); } // adjust highestRecordedUID if (dbFolderInfo) { if (m_curMsgUid > highestUID) { MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug, ("NormalEndHeaderParseStream(): Store new highest UID=%" PRIu32 " for folder=%s", m_curMsgUid, m_onlineFolderName.get())); dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, m_curMsgUid); } } if (m_isGmailServer) { nsCOMPtr flagState; aProtocol->GetFlagAndUidState(getter_AddRefs(flagState)); nsCString msgIDValue; nsCString threadIDValue; nsCString labelsValue; flagState->GetCustomAttribute(m_curMsgUid, "X-GM-MSGID"_ns, msgIDValue); flagState->GetCustomAttribute(m_curMsgUid, "X-GM-THRID"_ns, threadIDValue); flagState->GetCustomAttribute(m_curMsgUid, "X-GM-LABELS"_ns, labelsValue); newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue); newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue); newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue); } m_msgParser->Clear(); // clear out parser, because it holds onto a msg hdr. m_msgParser->SetMailDB(nullptr); // tell it to let go of the db too. // I don't think we want to do this - it does bad things like set the size // incorrectly. // m_msgParser->FinishHeader(); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream( nsIImapProtocol* aProtocol) { nsresult rv = NS_ERROR_FAILURE; return rv; } NS_IMETHODIMP nsImapMailFolder::BeginCopy() { NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER); nsresult rv; if (m_copyState->m_tmpFile) // leftover file spec nuke it { rv = m_copyState->m_tmpFile->Remove(false); if (NS_FAILED(rv)) { nsCString nativePath = m_copyState->m_tmpFile->HumanReadablePath(); MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't remove prev temp file %s: %" PRIx32, nativePath.get(), static_cast(rv))); } m_copyState->m_tmpFile = nullptr; } rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(m_copyState->m_tmpFile)); if (NS_FAILED(rv)) { MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("Couldn't create temp file: %" PRIx32, static_cast(rv))); OnCopyCompleted(m_copyState->m_srcSupport, rv); } NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr fileOutputStream; rv = MsgNewBufferedFileOutputStream( getter_AddRefs(m_copyState->m_msgFileStream), m_copyState->m_tmpFile, -1, 00600); if (NS_FAILED(rv)) MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create output file stream: %" PRIx32, static_cast(rv))); if (!m_copyState->m_dataBuffer) m_copyState->m_dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1); NS_ENSURE_TRUE(m_copyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY); m_copyState->m_dataBufferSize = COPY_BUFFER_SIZE; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::CopyDataToOutputStreamForAppend( nsIInputStream* aIStream, int32_t aLength, nsIOutputStream* outputStream) { uint32_t readCount; uint32_t writeCount; if (!m_copyState) m_copyState = new nsImapMailCopyState(); if (aLength + m_copyState->m_leftOver > m_copyState->m_dataBufferSize) { char* newBuffer = (char*)PR_REALLOC(m_copyState->m_dataBuffer, aLength + m_copyState->m_leftOver + 1); NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY); m_copyState->m_dataBuffer = newBuffer; m_copyState->m_dataBufferSize = aLength + m_copyState->m_leftOver; } char *start, *end; uint32_t linebreak_len = 1; nsresult rv = aIStream->Read( m_copyState->m_dataBuffer + m_copyState->m_leftOver, aLength, &readCount); if (NS_FAILED(rv)) return rv; m_copyState->m_leftOver += readCount; m_copyState->m_dataBuffer[m_copyState->m_leftOver] = '\0'; start = m_copyState->m_dataBuffer; if (m_copyState->m_eatLF) { if (*start == '\n') start++; m_copyState->m_eatLF = false; } end = PL_strpbrk(start, "\r\n"); if (end && *end == '\r' && *(end + 1) == '\n') linebreak_len = 2; while (start && end) { if (PL_strncasecmp(start, "X-Mozilla-Status:", 17) && PL_strncasecmp(start, "X-Mozilla-Status2:", 18) && PL_strncmp(start, "From - ", 7)) { rv = outputStream->Write(start, end - start, &writeCount); rv = outputStream->Write(CRLF, 2, &writeCount); } start = end + linebreak_len; if (start >= m_copyState->m_dataBuffer + m_copyState->m_leftOver) { m_copyState->m_leftOver = 0; break; } linebreak_len = 1; end = PL_strpbrk(start, "\r\n"); if (end && *end == '\r') { if (*(end + 1) == '\n') linebreak_len = 2; else if (!*(end + 1)) // block might have split CRLF so remember if m_copyState->m_eatLF = true; // we should eat LF } if (start && !end) { m_copyState->m_leftOver -= (start - m_copyState->m_dataBuffer); memcpy(m_copyState->m_dataBuffer, start, m_copyState->m_leftOver + 1); // including null } } return rv; } NS_IMETHODIMP nsImapMailFolder::CopyDataDone() { m_copyState = nullptr; return NS_OK; } // sICopyMessageListener methods, BeginCopy, CopyData, EndCopy, EndMove, // StartMessage, EndMessage NS_IMETHODIMP nsImapMailFolder::CopyData(nsIInputStream* aIStream, int32_t aLength) { NS_ENSURE_TRUE( m_copyState && m_copyState->m_msgFileStream && m_copyState->m_dataBuffer, NS_ERROR_NULL_POINTER); nsresult rv = CopyDataToOutputStreamForAppend(aIStream, aLength, m_copyState->m_msgFileStream); if (NS_FAILED(rv)) { MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyData failed: %" PRIx32, static_cast(rv))); OnCopyCompleted(m_copyState->m_srcSupport, rv); } return rv; } NS_IMETHODIMP nsImapMailFolder::EndCopy(bool copySucceeded) { nsresult rv = copySucceeded ? NS_OK : NS_ERROR_FAILURE; if (copySucceeded && m_copyState && m_copyState->m_msgFileStream) { nsCOMPtr urlListener; m_copyState->m_msgFileStream->Close(); // m_tmpFile can be stale because we wrote to it nsCOMPtr tmpFile; m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile)); m_copyState->m_tmpFile = tmpFile; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener)); rv = imapService->AppendMessageFromFile( m_copyState->m_tmpFile, this, EmptyCString(), true, m_copyState->m_selectedState, urlListener, m_copyState, m_copyState->m_msgWindow); } if (NS_FAILED(rv) || !copySucceeded) MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("EndCopy failed: %" PRIx32, static_cast(rv))); return rv; } NS_IMETHODIMP nsImapMailFolder::EndMove(bool moveSucceeded) { return NS_OK; } // this is the beginning of the next message copied NS_IMETHODIMP nsImapMailFolder::StartMessage() { return NS_OK; } // just finished the current message. NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key) { return NS_OK; } NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter* filter, nsIMsgWindow* msgWindow, bool* applyMore) { // // This routine is called indirectly from ApplyFiltersToHdr in two // circumstances, controlled by m_filterListRequiresBody: // // If false, after headers are parsed in NormalEndHeaderParseStream. // If true, after the message body is downloaded in NormalEndMsgWriteStream. // // In NormalEndHeaderParseStream, the message has not been added to the // database, and it is important that database notifications and count // updates do not occur. In NormalEndMsgWriteStream, the message has been // added to the database, and database notifications and count updates // should be performed. // NS_ENSURE_ARG_POINTER(filter); NS_ENSURE_ARG_POINTER(applyMore); nsresult rv = NS_OK; nsCOMPtr msgHdr; if (m_filterListRequiresBody) GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr)); else if (m_msgParser) m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr)); NS_ENSURE_TRUE(msgHdr, NS_ERROR_NULL_POINTER); // fatal error, cannot apply filters bool deleteToTrash = DeleteIsMoveToTrash(); nsTArray> filterActionList; rv = filter->GetSortedActionList(filterActionList); NS_ENSURE_SUCCESS(rv, rv); uint32_t numActions = filterActionList.Length(); nsCString msgId; msgHdr->GetMessageId(getter_Copies(msgId)); nsMsgKey msgKey; msgHdr->GetMessageKey(&msgKey); MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Applying %" PRIu32 " filter actions on message with key %" PRIu32, numActions, msgKeyToInt(msgKey))); MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("(Imap) Message ID: %s", msgId.get())); bool loggingEnabled = false; if (m_filterList && numActions) (void)m_filterList->GetLoggingEnabled(&loggingEnabled); bool msgIsNew = true; rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsresult finalResult = NS_OK; // result of all actions for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) { nsCOMPtr filterAction(filterActionList[actionIndex]); if (!filterAction) { MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning, ("(Imap) Filter action at index %" PRIu32 " invalid, skipping", actionIndex)); continue; } rv = NS_OK; // result of the current action nsMsgRuleActionType actionType; if (NS_SUCCEEDED(filterAction->GetType(&actionType))) { MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Running filter action at index %" PRIu32 ", action type = %i", actionIndex, actionType)); if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr); nsCString actionTargetFolderUri; if (actionType == nsMsgFilterAction::MoveToFolder || actionType == nsMsgFilterAction::CopyToFolder) { rv = filterAction->GetTargetFolderUri(actionTargetFolderUri); if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) { // clang-format off MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning, ("(Imap) Target URI for Copy/Move action is empty, skipping")); // clang-format on NS_ASSERTION(false, "actionTargetFolderUri is empty"); continue; } } uint32_t msgFlags; msgHdr->GetFlags(&msgFlags); bool isRead = (msgFlags & nsMsgMessageFlags::Read); switch (actionType) { case nsMsgFilterAction::Delete: { if (deleteToTrash) { // set value to trash folder nsCOMPtr mailTrash; rv = GetTrashFolder(getter_AddRefs(mailTrash)); if (NS_SUCCEEDED(rv) && mailTrash) { rv = mailTrash->GetURI(actionTargetFolderUri); if (NS_FAILED(rv)) break; } // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark // read in trash. } else { mDatabase->MarkHdrRead(msgHdr, true, nullptr); mDatabase->MarkImapDeleted(msgKey, true, nullptr); rv = StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true, {msgKey}, nullptr); if (NS_FAILED(rv)) break; // this will prevent us from adding the header to the db. m_msgMovedByFilter = true; } msgIsNew = false; } // note that delete falls through to move. [[fallthrough]]; case nsMsgFilterAction::MoveToFolder: { // if moving to a different file, do it. nsCString uri; rv = GetURI(uri); if (NS_FAILED(rv)) break; if (!actionTargetFolderUri.Equals(uri)) { msgHdr->GetFlags(&msgFlags); if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) { mDatabase->MarkMDNNeeded(msgKey, false, nullptr); mDatabase->MarkMDNSent(msgKey, true, nullptr); } nsresult rv = MoveIncorporatedMessage( msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow); if (NS_SUCCEEDED(rv)) { m_msgMovedByFilter = true; } else { if (loggingEnabled) { (void)filter->LogRuleHitFail(filterAction, msgHdr, rv, "filterFailureMoveFailed"_ns); } } } // don't apply any more filters, even if it was a move to the same // folder *applyMore = false; } break; case nsMsgFilterAction::CopyToFolder: { nsCString uri; rv = GetURI(uri); if (NS_FAILED(rv)) break; if (!actionTargetFolderUri.Equals(uri)) { // XXXshaver I'm not actually 100% what the right semantics are for // MDNs and copied messages, but I suspect deep down inside that // we probably want to suppress them only on the copies. msgHdr->GetFlags(&msgFlags); if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) { mDatabase->MarkMDNNeeded(msgKey, false, nullptr); mDatabase->MarkMDNSent(msgKey, true, nullptr); } nsCOMPtr dstFolder; rv = GetExistingFolder(actionTargetFolderUri, getter_AddRefs(dstFolder)); if (NS_FAILED(rv)) break; nsCOMPtr copyService = do_GetService( "@mozilla.org/messenger/messagecopyservice;1", &rv); if (NS_FAILED(rv)) break; rv = copyService->CopyMessages(this, {&*msgHdr}, dstFolder, false, nullptr, msgWindow, false); if (NS_FAILED(rv)) { if (loggingEnabled) { (void)filter->LogRuleHitFail(filterAction, msgHdr, rv, "filterFailureCopyFailed"_ns); } } } } break; case nsMsgFilterAction::MarkRead: { mDatabase->MarkHdrRead(msgHdr, true, nullptr); rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr); msgIsNew = false; } break; case nsMsgFilterAction::MarkUnread: { mDatabase->MarkHdrRead(msgHdr, false, nullptr); rv = StoreImapFlags(kImapMsgSeenFlag, false, {msgKey}, nullptr); msgIsNew = true; } break; case nsMsgFilterAction::MarkFlagged: { mDatabase->MarkHdrMarked(msgHdr, true, nullptr); rv = StoreImapFlags(kImapMsgFlaggedFlag, true, {msgKey}, nullptr); } break; case nsMsgFilterAction::KillThread: case nsMsgFilterAction::WatchThread: { nsCOMPtr msgThread; nsMsgKey threadKey; mDatabase->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread)); if (msgThread) { msgThread->GetThreadKey(&threadKey); if (actionType == nsMsgFilterAction::KillThread) rv = mDatabase->MarkThreadIgnored(msgThread, threadKey, true, nullptr); else rv = mDatabase->MarkThreadWatched(msgThread, threadKey, true, nullptr); } else { if (actionType == nsMsgFilterAction::KillThread) rv = msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored); else rv = msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Watched); } if (actionType == nsMsgFilterAction::KillThread) { mDatabase->MarkHdrRead(msgHdr, true, nullptr); rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr); msgIsNew = false; } } break; case nsMsgFilterAction::KillSubthread: { mDatabase->MarkHeaderKilled(msgHdr, true, nullptr); mDatabase->MarkHdrRead(msgHdr, true, nullptr); rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr); msgIsNew = false; } break; case nsMsgFilterAction::ChangePriority: { nsMsgPriorityValue filterPriority; // a int32_t filterAction->GetPriority(&filterPriority); rv = mDatabase->SetUint32PropertyByHdr( msgHdr, "priority", static_cast(filterPriority)); } break; case nsMsgFilterAction::AddTag: { nsCString keyword; filterAction->GetStrValue(keyword); rv = AddKeywordsToMessages({&*msgHdr}, keyword); } break; case nsMsgFilterAction::JunkScore: { nsAutoCString junkScoreStr; int32_t junkScore; filterAction->GetJunkScore(&junkScore); junkScoreStr.AppendInt(junkScore); rv = mDatabase->SetStringProperty(msgKey, "junkscore", junkScoreStr); mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter"_ns); // If score is available, set up to store junk status on server. if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE || junkScore == nsIJunkMailPlugin::IS_HAM_SCORE) { nsTArray* keysToClassify = m_moveCoalescer->GetKeyBucket( (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1); NS_ASSERTION(keysToClassify, "error getting key bucket"); if (keysToClassify) keysToClassify->AppendElement(msgKey); if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) { msgIsNew = false; mDatabase->MarkHdrNotNew(msgHdr, nullptr); // nsMsgDBFolder::SendFlagNotifications by the call to // SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages // only if the message is also read and database notifications // are active, but we are not going to mark it read in this // action, preferring to leave the choice to the user. // So correct numNewMessages. if (m_filterListRequiresBody) { msgHdr->GetFlags(&msgFlags); if (!(msgFlags & nsMsgMessageFlags::Read)) { int32_t numNewMessages; GetNumNewMessages(false, &numNewMessages); SetNumNewMessages(--numNewMessages); SetHasNewMessages(numNewMessages != 0); } } } } } break; case nsMsgFilterAction::Forward: { nsCString forwardTo; filterAction->GetStrValue(forwardTo); nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); if (NS_FAILED(rv)) break; if (!forwardTo.IsEmpty()) { nsCOMPtr compService = do_GetService("@mozilla.org/messengercompose;1", &rv); if (NS_FAILED(rv)) break; rv = compService->ForwardMessage( NS_ConvertUTF8toUTF16(forwardTo), msgHdr, msgWindow, server, nsIMsgComposeService::kForwardAsDefault); } } break; case nsMsgFilterAction::Reply: { nsCString replyTemplateUri; filterAction->GetStrValue(replyTemplateUri); nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); if (NS_FAILED(rv)) break; if (!replyTemplateUri.IsEmpty()) { nsCOMPtr compService = do_GetService("@mozilla.org/messengercompose;1", &rv); if (NS_SUCCEEDED(rv) && compService) { rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri, msgWindow, server); if (NS_FAILED(rv)) { NS_WARNING("ReplyWithTemplate failed"); if (rv == NS_ERROR_ABORT) { (void)filter->LogRuleHitFail( filterAction, msgHdr, rv, "filterFailureSendingReplyAborted"_ns); } else { (void)filter->LogRuleHitFail( filterAction, msgHdr, rv, "filterFailureSendingReplyError"_ns); } } } } } break; case nsMsgFilterAction::StopExecution: { // don't apply any more filters *applyMore = false; rv = NS_OK; } break; case nsMsgFilterAction::Custom: { nsCOMPtr customAction; rv = filterAction->GetCustomAction(getter_AddRefs(customAction)); if (NS_FAILED(rv)) break; nsAutoCString value; rv = filterAction->GetStrValue(value); if (NS_FAILED(rv)) break; rv = customAction->ApplyAction({&*msgHdr}, value, nullptr, nsMsgFilterType::InboxRule, msgWindow); // allow custom action to affect new msgHdr->GetFlags(&msgFlags); if (!(msgFlags & nsMsgMessageFlags::New)) msgIsNew = false; } break; default: NS_ERROR("unexpected filter action"); rv = NS_ERROR_UNEXPECTED; break; } } if (NS_FAILED(rv)) { finalResult = rv; MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, ("(Imap) Action execution failed with error: %" PRIx32, static_cast(rv))); if (loggingEnabled) { (void)filter->LogRuleHitFail(filterAction, msgHdr, rv, "filterFailureAction"_ns); } } else { MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Action execution succeeded")); } } if (!msgIsNew) { int32_t numNewMessages; GetNumNewMessages(false, &numNewMessages); // When database notifications are active, new counts will be reset // to zero in nsMsgDBFolder::SendFlagNotifications by the call to // SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here. if (!m_filterListRequiresBody) SetNumNewMessages(--numNewMessages); if (mDatabase) mDatabase->MarkHdrNotNew(msgHdr, nullptr); MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Message will not be marked new")); } MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Imap) Finished executing actions")); return finalResult; } NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char* uids, int32_t flags, nsIURI** url) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->SetMessageFlags(this, this, url, nsAutoCString(uids), flags, true); } // "this" is the parent folder NS_IMETHODIMP nsImapMailFolder::PlaybackOfflineFolderCreate( const nsAString& aFolderName, nsIMsgWindow* aWindow, nsIURI** url) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->CreateFolder(this, aFolderName, this, url); } NS_IMETHODIMP nsImapMailFolder::ReplayOfflineMoveCopy(const nsTArray& aMsgKeys, bool isMove, nsIMsgFolder* aDstFolder, nsIUrlListener* aUrlListener, nsIMsgWindow* aWindow, bool srcFolderOffline) { nsresult rv; nsCOMPtr imapFolder = do_QueryInterface(aDstFolder); if (imapFolder) { nsImapMailFolder* destImapFolder = static_cast(aDstFolder); nsCOMPtr dstFolderDB; aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB)); nsCOMPtr opsDb = do_QueryInterface(dstFolderDB, &rv); NS_ENSURE_SUCCESS(rv, rv); if (opsDb) { // find the fake header in the destination db, and use that to // set the pending attributes on the real headers. To do this, // we need to iterate over the offline ops in the destination db, // looking for ones with matching keys and source folder uri. // If we find that offline op, its "key" will be the key of the fake // header, so we just need to get the header for that key // from the dest db. nsTArray offlineOps; if (NS_SUCCEEDED(opsDb->ListAllOfflineOpIds(offlineOps))) { nsTArray> messages; nsCString srcFolderUri; GetURI(srcFolderUri); nsCOMPtr currentOp; for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++) { opsDb->GetOfflineOpForKey(offlineOps[opIndex], false, getter_AddRefs(currentOp)); if (currentOp) { nsCString opSrcUri; currentOp->GetSourceFolderURI(opSrcUri); if (opSrcUri.Equals(srcFolderUri)) { nsMsgKey srcMessageKey; currentOp->GetSrcMessageKey(&srcMessageKey); for (auto key : aMsgKeys) { if (srcMessageKey == key) { nsCOMPtr fakeDestHdr; dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex], getter_AddRefs(fakeDestHdr)); if (fakeDestHdr) messages.AppendElement(fakeDestHdr); break; } } } } } // 3rd parameter: Sets offline flag. destImapFolder->SetPendingAttributes(messages, isMove, srcFolderOffline); } } // if we can't get the dst folder db, we should still try to playback // the offline move/copy. } nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr resultUrl; nsAutoCString uids; AllocateUidStringFromKeys(aMsgKeys, uids); rv = imapService->OnlineMessageCopy(this, uids, aDstFolder, true, isMove, aUrlListener, getter_AddRefs(resultUrl), nullptr, aWindow); if (resultUrl) { nsCOMPtr mailnewsUrl = do_QueryInterface(resultUrl, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr folderListener = do_QueryInterface(aDstFolder); if (folderListener) mailnewsUrl->RegisterListener(folderListener); } return rv; } NS_IMETHODIMP nsImapMailFolder::AddMoveResultPseudoKey(nsMsgKey aMsgKey) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr pseudoHdr; rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(pseudoHdr)); NS_ENSURE_SUCCESS(rv, rv); nsCString messageId; pseudoHdr->GetMessageId(getter_Copies(messageId)); // err on the side of caution and ignore messages w/o messageid. if (messageId.IsEmpty()) return NS_OK; m_pseudoHdrs.InsertOrUpdate(messageId, aMsgKey); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags, const nsTArray& keys, nsIUrlListener* aUrlListener) { nsresult rv; if (!WeAreOffline()) { nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString msgIds; AllocateUidStringFromKeys(keys, msgIds); if (addFlags) imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this, msgIds, flags, true); else imapService->SubtractMessageFlags( this, aUrlListener ? aUrlListener : this, msgIds, flags, true); } else { rv = GetDatabase(); if (NS_SUCCEEDED(rv) && mDatabase) { nsCOMPtr opsDb = do_QueryInterface(mDatabase, &rv); NS_ENSURE_SUCCESS(rv, rv); for (auto key : keys) { nsCOMPtr op; rv = opsDb->GetOfflineOpForKey(key, true, getter_AddRefs(op)); SetFlag(nsMsgFolderFlags::OfflineEvents); if (NS_SUCCEEDED(rv) && op) { imapMessageFlagsType newFlags; op->GetNewFlags(&newFlags); op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags); } } opsDb->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline flags } } return rv; } NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener* aUrlListener, nsIMsgWindow* aMsgWindow) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr outUri; return imapService->LiteSelectFolder(this, aUrlListener, aMsgWindow, getter_AddRefs(outUri)); } nsresult nsImapMailFolder::GetFolderOwnerUserName(nsACString& userName) { if ((mFlags & nsMsgFolderFlags::ImapPersonal) || !(mFlags & (nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::ImapOtherUser))) { // this is one of our personal mail folders // return our username on this host nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); return NS_FAILED(rv) ? rv : server->GetUsername(userName); } // the only other type of owner is if it's in the other users' namespace if (!(mFlags & nsMsgFolderFlags::ImapOtherUser)) return NS_OK; if (m_ownerUserName.IsEmpty()) { nsCString onlineName; GetOnlineName(onlineName); m_ownerUserName = nsImapNamespaceList::GetFolderOwnerNameFromPath( GetNamespaceForFolder(), onlineName.get()); } userName = m_ownerUserName; return NS_OK; } nsImapNamespace* nsImapMailFolder::GetNamespaceForFolder() { if (!m_namespace) { #ifdef DEBUG_bienvenu // Make sure this isn't causing us to open the database NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown, "haven't set hierarchy delimiter"); #endif nsCString serverKey; nsCString onlineName; GetServerKey(serverKey); GetOnlineName(onlineName); char hierarchyDelimiter; GetHierarchyDelimiter(&hierarchyDelimiter); m_namespace = nsImapNamespaceList::GetNamespaceForFolder( serverKey.get(), onlineName.get(), hierarchyDelimiter); NS_ASSERTION(m_namespace, "didn't get namespace for folder"); if (m_namespace) { nsImapNamespaceList::SuggestHierarchySeparatorForNamespace( m_namespace, hierarchyDelimiter); m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace( serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace); } } return m_namespace; } void nsImapMailFolder::SetNamespaceForFolder(nsImapNamespace* ns) { #ifdef DEBUG_bienvenu NS_ASSERTION(ns, "null namespace"); #endif m_namespace = ns; } NS_IMETHODIMP nsImapMailFolder::FolderPrivileges(nsIMsgWindow* window) { NS_ENSURE_ARG_POINTER(window); nsresult rv = NS_OK; // if no window... if (!m_adminUrl.IsEmpty()) { nsCOMPtr extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); if (extProtService) { nsAutoCString scheme; nsCOMPtr uri; if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), m_adminUrl.get()))) return rv; uri->GetScheme(scheme); if (!scheme.IsEmpty()) { // if the URL scheme does not correspond to an exposed protocol, then we // need to hand this link click over to the external protocol handler. bool isExposed; rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed); if (NS_SUCCEEDED(rv) && !isExposed) return extProtService->LoadURI(uri, nullptr, nullptr, nullptr, false, false); } } } else { nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = imapService->GetFolderAdminUrl(this, window, this, nullptr); if (NS_SUCCEEDED(rv)) m_urlRunning = true; } return rv; } NS_IMETHODIMP nsImapMailFolder::GetHasAdminUrl(bool* aBool) { NS_ENSURE_ARG_POINTER(aBool); nsCOMPtr imapServer; nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer)); nsCString manageMailAccountUrl; if (NS_SUCCEEDED(rv) && imapServer) rv = imapServer->GetManageMailAccountUrl(manageMailAccountUrl); *aBool = (NS_SUCCEEDED(rv) && !manageMailAccountUrl.IsEmpty()); return rv; } NS_IMETHODIMP nsImapMailFolder::GetAdminUrl(nsACString& aResult) { aResult = m_adminUrl; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetAdminUrl(const nsACString& adminUrl) { m_adminUrl = adminUrl; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetHdrParser( nsIMsgParseMailMsgState** aHdrParser) { NS_ENSURE_ARG_POINTER(aHdrParser); NS_IF_ADDREF(*aHdrParser = m_msgParser); return NS_OK; } // this is used to issue an arbitrary imap command on the passed in msgs. // It assumes the command needs to be run in the selected state. NS_IMETHODIMP nsImapMailFolder::IssueCommandOnMsgs(const nsACString& command, const char* uids, nsIMsgWindow* aWindow, nsIURI** url) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->IssueCommandOnMsgs(this, aWindow, command, nsDependentCString(uids), url); } NS_IMETHODIMP nsImapMailFolder::FetchCustomMsgAttribute( const nsACString& attribute, const char* uids, nsIMsgWindow* aWindow, nsIURI** url) { nsresult rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->FetchCustomMsgAttribute(this, aWindow, attribute, nsDependentCString(uids), url); } nsresult nsImapMailFolder::MoveIncorporatedMessage( nsIMsgDBHdr* mailHdr, nsIMsgDatabase* sourceDB, const nsACString& destFolderUri, nsIMsgFilter* filter, nsIMsgWindow* msgWindow) { nsresult rv; if (m_moveCoalescer) { nsCOMPtr destIFolder; rv = GetOrCreateFolder(destFolderUri, getter_AddRefs(destIFolder)); NS_ENSURE_SUCCESS(rv, rv); if (destIFolder) { // check if the destination is a real folder (by checking for null parent) // and if it can file messages (e.g., servers or news folders can't file // messages). Or read only imap folders... bool canFileMessages = true; nsCOMPtr parentFolder; destIFolder->GetParent(getter_AddRefs(parentFolder)); if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages); if (filter && (!parentFolder || !canFileMessages)) { filter->SetEnabled(false); m_filterList->SaveToDefaultFile(); destIFolder->ThrowAlertMsg("filterDisabled", msgWindow); return NS_MSG_NOT_A_MAIL_FOLDER; } // put the header into the source db, since it needs to be there when we // copy it and we need a valid header to pass to // StartAsyncCopyMessagesInto nsMsgKey keyToFilter; mailHdr->GetMessageKey(&keyToFilter); if (sourceDB && destIFolder) { bool imapDeleteIsMoveToTrash = DeleteIsMoveToTrash(); m_moveCoalescer->AddMove(destIFolder, keyToFilter); // For each folder, we need to keep track of the ids we want to move to // that folder - we used to store them in the MSG_FolderInfo and then // when we'd finished downloading headers, we'd iterate through all the // folders looking for the ones that needed messages moved into them - // perhaps instead we could keep track of nsIMsgFolder, // nsTArray pairs here in the imap code. nsTArray // *idsToMoveFromInbox = msgFolder->GetImapIdsToMoveFromInbox(); // idsToMoveFromInbox->AppendElement(keyToFilter); if (imapDeleteIsMoveToTrash) { } bool isRead = false; mailHdr->GetIsRead(&isRead); if (imapDeleteIsMoveToTrash) rv = NS_OK; } } } else rv = NS_ERROR_UNEXPECTED; // we have to return an error because we do not actually move the message // it is done async and that can fail return rv; } /** * This method assumes that key arrays and flag states are sorted by increasing * key. */ void nsImapMailFolder::FindKeysToDelete(const nsTArray& existingKeys, nsTArray& keysToDelete, nsIImapFlagAndUidState* flagState, uint32_t boxFlags) { bool showDeletedMessages = ShowDeletedMessages(); int32_t numMessageInFlagState; bool partialUIDFetch; uint32_t uidOfMessage; imapMessageFlagsType flags; flagState->GetNumberOfMessages(&numMessageInFlagState); flagState->GetPartialUIDFetch(&partialUIDFetch); // if we're doing a partialUIDFetch, just delete the keys from the db // that have the deleted flag set (if not using imap delete model) // and return. if (partialUIDFetch) { if (!showDeletedMessages) { for (uint32_t i = 0; (int32_t)i < numMessageInFlagState; i++) { flagState->GetUidOfMessage(i, &uidOfMessage); // flag state will be zero filled up to first real uid, so ignore those. if (uidOfMessage) { flagState->GetMessageFlags(i, &flags); if (flags & kImapMsgDeletedFlag) keysToDelete.AppendElement(uidOfMessage); } } } else if (boxFlags & kJustExpunged) { // we've just issued an expunge with a partial flag state. We should // delete headers with the imap deleted flag set, because we can't // tell from the expunge response which messages were deleted. nsCOMPtr hdrs; nsresult rv = GetMessages(getter_AddRefs(hdrs)); NS_ENSURE_SUCCESS_VOID(rv); bool hasMore = false; while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr header; rv = hdrs->GetNext(getter_AddRefs(header)); NS_ENSURE_SUCCESS_VOID(rv); uint32_t msgFlags; header->GetFlags(&msgFlags); if (msgFlags & nsMsgMessageFlags::IMAPDeleted) { nsMsgKey msgKey; header->GetMessageKey(&msgKey); keysToDelete.AppendElement(msgKey); } } } return; } // otherwise, we have a complete set of uid's and flags, so we delete // anything that's in existingKeys but not in the flag state, as well // as messages with the deleted flag set. uint32_t total = existingKeys.Length(); int onlineIndex = 0; // current index into flagState for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) { while ( (onlineIndex < numMessageInFlagState) && NS_SUCCEEDED(flagState->GetUidOfMessage(onlineIndex, &uidOfMessage)) && (existingKeys[keyIndex] > uidOfMessage)) onlineIndex++; flagState->GetUidOfMessage(onlineIndex, &uidOfMessage); flagState->GetMessageFlags(onlineIndex, &flags); // delete this key if it is not there or marked deleted if ((onlineIndex >= numMessageInFlagState) || (existingKeys[keyIndex] != uidOfMessage) || ((flags & kImapMsgDeletedFlag) && !showDeletedMessages)) { nsMsgKey doomedKey = existingKeys[keyIndex]; if ((int32_t)doomedKey <= 0 && doomedKey != nsMsgKey_None) continue; keysToDelete.AppendElement(existingKeys[keyIndex]); } flagState->GetUidOfMessage(onlineIndex, &uidOfMessage); if (existingKeys[keyIndex] == uidOfMessage) onlineIndex++; } } void nsImapMailFolder::FindKeysToAdd(const nsTArray& existingKeys, nsTArray& keysToFetch, uint32_t& numNewUnread, nsIImapFlagAndUidState* flagState) { bool showDeletedMessages = ShowDeletedMessages(); int dbIndex = 0; // current index into existingKeys int32_t existTotal, numberOfKnownKeys; int32_t messageIndex; numNewUnread = 0; existTotal = numberOfKnownKeys = existingKeys.Length(); flagState->GetNumberOfMessages(&messageIndex); bool partialUIDFetch; flagState->GetPartialUIDFetch(&partialUIDFetch); for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) { uint32_t uidOfMessage; flagState->GetUidOfMessage(flagIndex, &uidOfMessage); while ((flagIndex < numberOfKnownKeys) && (dbIndex < existTotal) && existingKeys[dbIndex] < uidOfMessage) dbIndex++; if ((flagIndex >= numberOfKnownKeys) || (dbIndex >= existTotal) || (existingKeys[dbIndex] != uidOfMessage)) { numberOfKnownKeys++; imapMessageFlagsType flags; flagState->GetMessageFlags(flagIndex, &flags); NS_ASSERTION(uidOfMessage != nsMsgKey_None, "got invalid msg key"); if (uidOfMessage && uidOfMessage != nsMsgKey_None && (showDeletedMessages || !(flags & kImapMsgDeletedFlag))) { if (mDatabase) { bool dbContainsKey; if (NS_SUCCEEDED( mDatabase->ContainsKey(uidOfMessage, &dbContainsKey)) && dbContainsKey) { // this is expected in the partial uid fetch case because the // flag state does not contain all messages, so the db has // messages the flag state doesn't know about. if (!partialUIDFetch) NS_ERROR("db has key - flagState messed up?"); continue; } } keysToFetch.AppendElement(uidOfMessage); if (!(flags & kImapMsgSeenFlag)) numNewUnread++; } } } } NS_IMETHODIMP nsImapMailFolder::GetMsgHdrsToDownload( bool* aMoreToDownload, int32_t* aTotalCount, nsTArray& aKeys) { NS_ENSURE_ARG_POINTER(aMoreToDownload); NS_ENSURE_ARG_POINTER(aTotalCount); aKeys.Clear(); *aMoreToDownload = false; *aTotalCount = m_totalKeysToFetch; if (m_keysToFetch.IsEmpty()) { return NS_OK; } // if folder isn't open in a window, no reason to limit the number of headers // we download. nsCOMPtr session = do_GetService("@mozilla.org/messenger/services/session;1"); bool folderOpen = false; if (session) session->IsFolderOpenInWindow(this, &folderOpen); int32_t hdrChunkSize = 200; if (folderOpen) { nsresult rv; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); if (prefBranch) prefBranch->GetIntPref("mail.imap.hdr_chunk_size", &hdrChunkSize); } int32_t numKeysToFetch = m_keysToFetch.Length(); int32_t startIndex = 0; if (folderOpen && hdrChunkSize > 0 && (int32_t)m_keysToFetch.Length() > hdrChunkSize) { numKeysToFetch = hdrChunkSize; *aMoreToDownload = true; startIndex = m_keysToFetch.Length() - hdrChunkSize; } aKeys.AppendElements(&m_keysToFetch[startIndex], numKeysToFetch); // Remove these for the incremental header download case, so that // we know we don't have to download them again. m_keysToFetch.RemoveElementsAt(startIndex, numKeysToFetch); return NS_OK; } void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol) { // now, tell it we don't need any bodies. nsTArray noBodies; aProtocol->NotifyBodysToDownload(noBodies); } void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr* tweakMe) { if (mDatabase && aProtocol && tweakMe) { tweakMe->SetMessageKey(m_curMsgUid); tweakMe->SetMessageSize(m_nextMessageByteLength); bool foundIt = false; nsCOMPtr flagState; nsresult rv = aProtocol->GetFlagAndUidState(getter_AddRefs(flagState)); NS_ENSURE_SUCCESS_VOID(rv); rv = flagState->HasMessage(m_curMsgUid, &foundIt); if (NS_SUCCEEDED(rv) && foundIt) { imapMessageFlagsType imap_flags; nsCString customFlags; flagState->GetMessageFlagsByUid(m_curMsgUid, &imap_flags); if (imap_flags & kImapMsgCustomKeywordFlag) { flagState->GetCustomFlags(m_curMsgUid, getter_Copies(customFlags)); } // make a mask and clear these message flags uint32_t mask = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied | nsMsgMessageFlags::Marked | nsMsgMessageFlags::IMAPDeleted | nsMsgMessageFlags::Labels; uint32_t dbHdrFlags; tweakMe->GetFlags(&dbHdrFlags); tweakMe->AndFlags(~mask, &dbHdrFlags); // set the new value for these flags uint32_t newFlags = 0; if (imap_flags & kImapMsgSeenFlag) newFlags |= nsMsgMessageFlags::Read; else // if (imap_flags & kImapMsgRecentFlag) newFlags |= nsMsgMessageFlags::New; // Okay here is the MDN needed logic (if DNT header seen): /* if server support user defined flag: XXX TODO: Fix badly formatted comment which doesn't reflect the code. MDNSent flag set => clear kMDNNeeded flag MDNSent flag not set => do nothing, leave kMDNNeeded on else if not nsMsgMessageFlags::New => clear kMDNNeeded flag nsMsgMessageFlags::New => do nothing, leave kMDNNeeded on */ uint16_t userFlags; rv = aProtocol->GetSupportedUserFlags(&userFlags); if (NS_SUCCEEDED(rv) && (userFlags & (kImapMsgSupportUserFlag | kImapMsgSupportMDNSentFlag))) { if (imap_flags & kImapMsgMDNSentFlag) { newFlags |= nsMsgMessageFlags::MDNReportSent; if (dbHdrFlags & nsMsgMessageFlags::MDNReportNeeded) tweakMe->AndFlags(~nsMsgMessageFlags::MDNReportNeeded, &dbHdrFlags); } } if (imap_flags & kImapMsgAnsweredFlag) newFlags |= nsMsgMessageFlags::Replied; if (imap_flags & kImapMsgFlaggedFlag) newFlags |= nsMsgMessageFlags::Marked; if (imap_flags & kImapMsgDeletedFlag) newFlags |= nsMsgMessageFlags::IMAPDeleted; if (imap_flags & kImapMsgForwardedFlag) newFlags |= nsMsgMessageFlags::Forwarded; if (newFlags) tweakMe->OrFlags(newFlags, &dbHdrFlags); if (!customFlags.IsEmpty()) (void)HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags); } } } NS_IMETHODIMP nsImapMailFolder::SetupMsgWriteStream(nsIFile* aFile, bool addDummyEnvelope) { nsresult rv; aFile->Remove(false); m_tempMessageStreamBytesWritten = 0; rv = MsgNewBufferedFileOutputStream( getter_AddRefs(m_tempMessageStream), aFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00700); if (m_tempMessageStream && addDummyEnvelope) { nsAutoCString result; char* ct; uint32_t writeCount; time_t now = time((time_t*)0); ct = ctime(&now); ct[24] = 0; result = "From - "; result += ct; result += MSG_LINEBREAK; rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount); NS_ENSURE_SUCCESS(rv, rv); m_tempMessageStreamBytesWritten += writeCount; result = "X-Mozilla-Status: 0001"; result += MSG_LINEBREAK; rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount); NS_ENSURE_SUCCESS(rv, rv); m_tempMessageStreamBytesWritten += writeCount; result = "X-Mozilla-Status2: 00000000"; result += MSG_LINEBREAK; rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount); NS_ENSURE_SUCCESS(rv, rv); m_tempMessageStreamBytesWritten += writeCount; } return rv; } NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline( nsTArray> const& messages, nsIMsgWindow* window) { nsAutoCString messageIds; nsTArray srcKeyArray; nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray); if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = AcquireSemaphore(static_cast(this)); if (NS_FAILED(rv)) { ThrowAlertMsg("operationFailedFolderBusy", window); return rv; } return imapService->DownloadMessagesForOffline(messageIds, this, this, window); } NS_IMETHODIMP nsImapMailFolder::DownloadAllForOffline(nsIUrlListener* listener, nsIMsgWindow* msgWindow) { nsresult rv; nsCOMPtr runningURI; bool noSelect; GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect); if (!noSelect) { nsAutoCString messageIdsToDownload; nsTArray msgsToDownload; GetDatabase(); m_downloadingFolderForOfflineUse = true; rv = AcquireSemaphore(static_cast(this)); if (NS_FAILED(rv)) { m_downloadingFolderForOfflineUse = false; ThrowAlertMsg("operationFailedFolderBusy", msgWindow); return rv; } nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // Selecting the folder with nsIImapUrl::shouldStoreMsgOffline true will // cause us to fetch any message bodies we don't have. m_urlListener = listener; rv = imapService->SelectFolder(this, this, msgWindow, getter_AddRefs(runningURI)); if (NS_SUCCEEDED(rv)) { nsCOMPtr imapUrl(do_QueryInterface(runningURI)); if (imapUrl) imapUrl->SetStoreResultsOffline(true); m_urlRunning = true; } } else rv = NS_MSG_FOLDER_UNREADABLE; return rv; } NS_IMETHODIMP nsImapMailFolder::ParseAdoptedMsgLine(const char* adoptedMessageLine, nsMsgKey uidOfMessage, nsIImapUrl* aImapUrl) { NS_ENSURE_ARG_POINTER(aImapUrl); uint32_t count = 0; nsresult rv; // remember the uid of the message we're downloading. m_curMsgUid = uidOfMessage; if (!m_offlineHeader) { rv = GetMessageHeader(uidOfMessage, getter_AddRefs(m_offlineHeader)); if (NS_SUCCEEDED(rv) && !m_offlineHeader) rv = NS_ERROR_UNEXPECTED; NS_ENSURE_SUCCESS(rv, rv); rv = StartNewOfflineMessage(); NS_ENSURE_SUCCESS(rv, rv); } // adoptedMessageLine is actually a string with a lot of message lines, // separated by native line terminators we need to count the number of // MSG_LINEBREAK's to determine how much to increment m_numOfflineMsgLines by. const char* nextLine = adoptedMessageLine; do { m_numOfflineMsgLines++; nextLine = PL_strstr(nextLine, MSG_LINEBREAK); if (nextLine) nextLine += MSG_LINEBREAK_LEN; } while (nextLine && *nextLine); if (m_tempMessageStream) { rv = m_tempMessageStream->Write(adoptedMessageLine, PL_strlen(adoptedMessageLine), &count); NS_ENSURE_SUCCESS(rv, rv); m_tempMessageStreamBytesWritten += count; } return NS_OK; } void nsImapMailFolder::EndOfflineDownload() { if (m_tempMessageStream) { m_tempMessageStream->Close(); m_tempMessageStream = nullptr; ReleaseSemaphore(static_cast(this)); if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } m_offlineHeader = nullptr; } NS_IMETHODIMP nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage, bool markRead, nsIImapUrl* imapUrl, int32_t updatedMessageSize) { if (updatedMessageSize != -1) { // retrieve the message header to update size, if we don't already have it nsCOMPtr msgHeader = m_offlineHeader; if (!msgHeader) GetMessageHeader(uidOfMessage, getter_AddRefs(msgHeader)); if (msgHeader) { uint32_t msgSize; msgHeader->GetMessageSize(&msgSize); MOZ_LOG(IMAP, mozilla::LogLevel::Debug, ("Updating stored message size from %u, new size %d", msgSize, updatedMessageSize)); msgHeader->SetMessageSize(updatedMessageSize); // only commit here if this isn't an offline message // offline header gets committed in EndNewOfflineMessage() called below if (mDatabase && !m_offlineHeader) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } else NS_WARNING( "Failed to get message header when trying to update message size"); } if (m_offlineHeader) EndNewOfflineMessage(NS_OK); m_curMsgUid = uidOfMessage; // Apply filter now if it needed a body if (m_filterListRequiresBody) { if (m_filterList) { nsCOMPtr newMsgHdr; GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr)); GetMoveCoalescer(); nsCOMPtr msgWindow; if (imapUrl) { nsresult rv; nsCOMPtr msgUrl; msgUrl = do_QueryInterface(imapUrl, &rv); if (msgUrl && NS_SUCCEEDED(rv)) msgUrl->GetMsgWindow(getter_AddRefs(msgWindow)); } m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr, this, mDatabase, EmptyCString(), this, msgWindow); NotifyFolderEvent(kFiltersApplied); } // Process filter plugins and other items normally done at the end of // HeaderFetchCompleted. bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves(); PlaybackCoalescedOperations(); bool filtersRun; CallFilterPlugins(nullptr, &filtersRun); int32_t numNewBiffMsgs = 0; if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs); if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 && (!pendingMoves || !ShowPreviewText())) { // If we are performing biff for this folder, tell the // stand-alone biff about the new high water mark // We must ensure that the server knows that we are performing biff. // Otherwise the stand-alone biff won't fire. nsCOMPtr server; if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) server->SetPerformingBiff(true); SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail); if (server) server->SetPerformingBiff(false); m_performingBiff = false; } if (m_filterList) (void)m_filterList->FlushLogIfNecessary(); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::AbortMsgWriteStream() { m_offlineHeader = nullptr; return NS_ERROR_FAILURE; } // message move/copy related methods NS_IMETHODIMP nsImapMailFolder::OnlineCopyCompleted(nsIImapProtocol* aProtocol, ImapOnlineCopyState aCopyState) { NS_ENSURE_ARG_POINTER(aProtocol); nsresult rv; if (aCopyState == ImapOnlineCopyStateType::kSuccessfulCopy) { nsCOMPtr imapUrl; rv = aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl)); if (NS_FAILED(rv) || !imapUrl) return NS_ERROR_FAILURE; nsImapAction action; rv = imapUrl->GetImapAction(&action); if (NS_FAILED(rv)) return rv; if (action != nsIImapUrl::nsImapOnlineToOfflineMove) return NS_ERROR_FAILURE; // don't assert here... nsCString messageIds; rv = imapUrl->GetListOfMessageIds(messageIds); if (NS_FAILED(rv)) return rv; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); return imapService->AddMessageFlags(this, nullptr, messageIds, kImapMsgDeletedFlag, true); } /* unhandled copystate */ if (m_copyState) // whoops, this is the wrong folder - should use the source // folder { nsCOMPtr srcFolder; srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv); if (srcFolder) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted); } else rv = NS_ERROR_FAILURE; return rv; } NS_IMETHODIMP nsImapMailFolder::CloseMockChannel(nsIImapMockChannel* aChannel) { aChannel->Close(); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::ReleaseUrlCacheEntry(nsIMsgMailNewsUrl* aUrl) { NS_ENSURE_ARG_POINTER(aUrl); return aUrl->SetMemCacheEntry(nullptr); } NS_IMETHODIMP nsImapMailFolder::BeginMessageUpload() { return NS_ERROR_FAILURE; } nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage, nsIMsgDBHdr* dbHdr, uint16_t userFlags, nsCString& keywords) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); ToLowerCase(keywords); bool messageClassified = true; // ### TODO: we really should parse the keywords into space delimited keywords // before checking // Mac Mail, Yahoo uses "NotJunk" if (FindInReadable("NonJunk"_ns, keywords, nsCaseInsensitiveCStringComparator) || FindInReadable("NotJunk"_ns, keywords, nsCaseInsensitiveCStringComparator)) { nsAutoCString msgJunkScore; msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE); mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore); } else if (FindInReadable("Junk"_ns, keywords, nsCaseInsensitiveCStringComparator)) { uint32_t newFlags; dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); nsAutoCString msgJunkScore; msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE); mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore); } else messageClassified = false; if (messageClassified) { // only set the junkscore origin if it wasn't set before. nsCString existingProperty; dbHdr->GetStringProperty("junkscoreorigin", existingProperty); if (existingProperty.IsEmpty()) dbHdr->SetStringProperty("junkscoreorigin", "imapflag"_ns); } if (!(userFlags & kImapMsgSupportUserFlag)) { nsCString localKeywords; nsCString prevKeywords; dbHdr->GetStringProperty("keywords", localKeywords); dbHdr->GetStringProperty("prevkeywords", prevKeywords); // localKeywords: tags currently stored in database for the message. // keywords: tags stored in server and obtained when flags for the message // were last fetched. (Parameter of this function.) // prevKeywords: saved keywords from previous call of this function. // clang-format off MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug, ("UID=%" PRIu32 ", localKeywords=|%s| keywords=|%s|, prevKeywords=|%s|", uidOfMessage, localKeywords.get(), keywords.get(), prevKeywords.get())); // clang-format on // Store keywords to detect changes on next call of this function. dbHdr->SetStringProperty("prevkeywords", keywords); // Parse the space separated strings into arrays. nsTArray localKeywordArray; nsTArray keywordArray; nsTArray prevKeywordArray; ParseString(localKeywords, ' ', localKeywordArray); ParseString(keywords, ' ', keywordArray); ParseString(prevKeywords, ' ', prevKeywordArray); // If keyword not received now but was the last time, remove it from // the localKeywords. This means the keyword was removed by another user // sharing the folder. for (uint32_t i = 0; i < prevKeywordArray.Length(); i++) { bool inRcvd = keywordArray.Contains(prevKeywordArray[i]); bool inLocal = localKeywordArray.Contains(prevKeywordArray[i]); if (!inRcvd && inLocal) localKeywordArray.RemoveElement(prevKeywordArray[i]); } // Combine local and rcvd keyword arrays into a single string // so it can be passed to SetStringProperty(). If element of // local already in rcvd, avoid duplicates in combined string. nsAutoCString combinedKeywords; for (uint32_t i = 0; i < localKeywordArray.Length(); i++) { if (!keywordArray.Contains(localKeywordArray[i])) { combinedKeywords.Append(localKeywordArray[i]); combinedKeywords.Append(' '); } } for (uint32_t i = 0; i < keywordArray.Length(); i++) { combinedKeywords.Append(keywordArray[i]); combinedKeywords.Append(' '); } MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug, ("combinedKeywords stored = |%s|", combinedKeywords.get())); // combinedKeywords are tags being stored in database for the message. return dbHdr->SetStringProperty("keywords", combinedKeywords); } return (userFlags & kImapMsgSupportUserFlag) ? dbHdr->SetStringProperty("keywords", keywords) : NS_OK; } // synchronize the message flags in the database with the server flags nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState* flagState) { nsresult rv = GetDatabase(); // we need a database for this NS_ENSURE_SUCCESS(rv, rv); bool partialUIDFetch; flagState->GetPartialUIDFetch(&partialUIDFetch); // update all of the database flags int32_t messageIndex; uint32_t messageSize; // Take this opportunity to recalculate the folder size, if we're not a // partial (condstore) fetch. int64_t newFolderSize = 0; flagState->GetNumberOfMessages(&messageIndex); uint16_t supportedUserFlags; flagState->GetSupportedUserFlags(&supportedUserFlags); for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) { uint32_t uidOfMessage; flagState->GetUidOfMessage(flagIndex, &uidOfMessage); imapMessageFlagsType flags; flagState->GetMessageFlags(flagIndex, &flags); bool containsKey; rv = mDatabase->ContainsKey(uidOfMessage, &containsKey); // if we don't have the header, don't diddle the flags. // GetMsgHdrForKey will create the header if it doesn't exist. if (NS_FAILED(rv) || !containsKey) continue; nsCOMPtr dbHdr; rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr)); if (NS_FAILED(rv)) continue; if (NS_SUCCEEDED(dbHdr->GetMessageSize(&messageSize))) newFolderSize += messageSize; nsCString keywords; if (NS_SUCCEEDED( flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords)))) HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords); NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags); } if (!partialUIDFetch && newFolderSize != mFolderSize) { int64_t oldFolderSize = mFolderSize; mFolderSize = newFolderSize; NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize); } return NS_OK; } // helper routine to sync the flags on a given header nsresult nsImapMailFolder::NotifyMessageFlagsFromHdr(nsIMsgDBHdr* dbHdr, nsMsgKey msgKey, uint32_t flags) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); // Although it may seem strange to keep a local reference of mDatabase here, // the current lifetime management of databases requires that methods // sometimes null the database when they think they opened it. Unfortunately // experience shows this happens when we don't expect, so for crash protection // best practice with the current flawed database management is to keep a // local reference when there will be complex calls in a method. See bug // 1312254. nsCOMPtr database(mDatabase); NS_ENSURE_STATE(database); database->MarkHdrRead(dbHdr, (flags & kImapMsgSeenFlag) != 0, nullptr); database->MarkHdrReplied(dbHdr, (flags & kImapMsgAnsweredFlag) != 0, nullptr); database->MarkHdrMarked(dbHdr, (flags & kImapMsgFlaggedFlag) != 0, nullptr); database->MarkImapDeleted(msgKey, (flags & kImapMsgDeletedFlag) != 0, nullptr); uint32_t supportedFlags; GetSupportedUserFlags(&supportedFlags); if (supportedFlags & kImapMsgSupportForwardedFlag) database->MarkForwarded(msgKey, (flags & kImapMsgForwardedFlag) != 0, nullptr); if (supportedFlags & kImapMsgSupportMDNSentFlag) database->MarkMDNSent(msgKey, (flags & kImapMsgMDNSentFlag) != 0, nullptr); return NS_OK; } // message flags operation - this is called from the imap protocol, // proxied over from the imap thread to the ui thread, when a flag changes NS_IMETHODIMP nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags, const nsACString& aKeywords, nsMsgKey aMsgKey, uint64_t aHighestModSeq) { if (NS_SUCCEEDED(GetDatabase()) && mDatabase) { bool msgDeleted = aFlags & kImapMsgDeletedFlag; if (aHighestModSeq || msgDeleted) { nsCOMPtr dbFolderInfo; mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (dbFolderInfo) { if (aHighestModSeq) { char intStrBuf[40]; PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", aHighestModSeq); MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug, ("NotifyMessageFlags(): Store highest MODSEQ=%" PRIu64 " for folder=%s", aHighestModSeq, m_onlineFolderName.get())); dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf)); } if (msgDeleted) { uint32_t oldDeletedCount; dbFolderInfo->GetUint32Property(kDeletedHdrCountPropertyName, 0, &oldDeletedCount); dbFolderInfo->SetUint32Property(kDeletedHdrCountPropertyName, oldDeletedCount + 1); } } } nsCOMPtr dbHdr; bool containsKey; nsresult rv = mDatabase->ContainsKey(aMsgKey, &containsKey); // if we don't have the header, don't diddle the flags. // GetMsgHdrForKey will create the header if it doesn't exist. if (NS_FAILED(rv) || !containsKey) return rv; rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(dbHdr)); if (NS_SUCCEEDED(rv) && dbHdr) { uint32_t supportedUserFlags; GetSupportedUserFlags(&supportedUserFlags); NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags); nsCString keywords(aKeywords); HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords); } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::NotifyMessageDeleted(const char* onlineFolderName, bool deleteAllMsgs, const char* msgIdString) { if (deleteAllMsgs) return NS_OK; if (!msgIdString) return NS_OK; nsTArray affectedMessages; ParseUidString(msgIdString, affectedMessages); if (!ShowDeletedMessages()) { GetDatabase(); NS_ENSURE_TRUE(mDatabase, NS_OK); if (!ShowDeletedMessages()) { if (!affectedMessages.IsEmpty()) // perhaps Search deleted these messages { DeleteStoreMessages(affectedMessages); mDatabase->DeleteMessages(affectedMessages, nullptr); } } else // && !imapDeleteIsMoveToTrash // TODO: can this ever be executed? SetIMAPDeletedFlag(mDatabase, affectedMessages, false); } return NS_OK; } bool nsImapMailFolder::ShowDeletedMessages() { nsresult rv; nsCOMPtr hostSession = do_GetService(kCImapHostSessionList, &rv); NS_ENSURE_SUCCESS(rv, false); bool showDeleted = false; nsCString serverKey; GetServerKey(serverKey); hostSession->GetShowDeletedMessagesForHost(serverKey.get(), showDeleted); return showDeleted; } bool nsImapMailFolder::DeleteIsMoveToTrash() { nsresult err; nsCOMPtr hostSession = do_GetService(kCImapHostSessionList, &err); NS_ENSURE_SUCCESS(err, true); bool rv = true; nsCString serverKey; GetServerKey(serverKey); hostSession->GetDeleteIsMoveToTrashForHost(serverKey.get(), rv); return rv; } nsresult nsImapMailFolder::GetTrashFolder(nsIMsgFolder** pTrashFolder) { NS_ENSURE_ARG_POINTER(pTrashFolder); nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && rootFolder) { rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder); if (!*pTrashFolder) rv = NS_ERROR_FAILURE; } return rv; } // store nsMsgMessageFlags::IMAPDeleted in the specified mailhdr records void nsImapMailFolder::SetIMAPDeletedFlag(nsIMsgDatabase* mailDB, const nsTArray& msgids, bool markDeleted) { nsresult markStatus = NS_OK; uint32_t total = msgids.Length(); for (uint32_t msgIndex = 0; NS_SUCCEEDED(markStatus) && (msgIndex < total); msgIndex++) markStatus = mailDB->MarkImapDeleted(msgids[msgIndex], markDeleted, nullptr); } NS_IMETHODIMP nsImapMailFolder::GetMessageSizeFromDB(const char* id, uint32_t* size) { NS_ENSURE_ARG_POINTER(size); *size = 0; nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); if (id) { nsMsgKey key = msgKeyFromInt(ParseUint64Str(id)); nsCOMPtr mailHdr; rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(mailHdr)); if (NS_SUCCEEDED(rv) && mailHdr) rv = mailHdr->GetMessageSize(size); } return rv; } NS_IMETHODIMP nsImapMailFolder::GetCurMoveCopyMessageInfo(nsIImapUrl* runningUrl, PRTime* aDate, nsACString& aKeywords, uint32_t* aResult) { nsCOMPtr copyState; runningUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) { nsCOMPtr mailCopyState = do_QueryInterface(copyState); uint32_t supportedFlags = 0; GetSupportedUserFlags(&supportedFlags); if (mailCopyState && mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) { nsIMsgDBHdr* message = mailCopyState->m_messages[mailCopyState->m_curIndex]; message->GetFlags(aResult); if (aDate) message->GetDate(aDate); if (supportedFlags & kImapMsgSupportUserFlag) { // setup the custom imap keywords, which includes the message keywords // plus any junk status nsCString junkscore; message->GetStringProperty("junkscore", junkscore); bool isJunk = false, isNotJunk = false; if (!junkscore.IsEmpty()) { if (junkscore.EqualsLiteral("0")) isNotJunk = true; else isJunk = true; } nsCString keywords; // MsgFindKeyword can't use nsACString message->GetStringProperty("keywords", keywords); int32_t start; int32_t length; bool hasJunk = MsgFindKeyword("junk"_ns, keywords, &start, &length); if (hasJunk && !isJunk) keywords.Cut(start, length); else if (!hasJunk && isJunk) keywords.AppendLiteral(" Junk"); bool hasNonJunk = MsgFindKeyword("nonjunk"_ns, keywords, &start, &length); if (!hasNonJunk) hasNonJunk = MsgFindKeyword("notjunk"_ns, keywords, &start, &length); if (hasNonJunk && !isNotJunk) keywords.Cut(start, length); else if (!hasNonJunk && isNotJunk) keywords.AppendLiteral(" NonJunk"); // Cleanup extra spaces while (!keywords.IsEmpty() && keywords.First() == ' ') keywords.Cut(0, 1); while (!keywords.IsEmpty() && keywords.Last() == ' ') keywords.Cut(keywords.Length() - 1, 1); while (!keywords.IsEmpty() && (start = keywords.Find(" "_ns)) >= 0) keywords.Cut(start, 1); aKeywords.Assign(keywords); } } // if we don't have a source header, and it's not the drafts folder, // then mark the message read, since it must be an append to the // fcc or templates folder. else if (mailCopyState) { *aResult = mailCopyState->m_newMsgFlags; if (supportedFlags & kImapMsgSupportUserFlag) aKeywords.Assign(mailCopyState->m_newMsgKeywords); } } return NS_OK; } // nsIUrlListener implementation. NS_IMETHODIMP nsImapMailFolder::OnStartRunningUrl(nsIURI* aUrl) { NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url"); nsCOMPtr mailUrl = do_QueryInterface(aUrl); if (mailUrl) { bool updatingFolder; mailUrl->GetUpdatingFolder(&updatingFolder); m_updatingFolder = updatingFolder; } m_urlRunning = true; return NS_OK; } // nsIUrlListener implementation. // nsImapMailFolder passes itself as a listener when it kicks off operations // on the nsIImapService. So, when the operation completes, this gets called // to handle all the different operations, using a big switch statement. NS_IMETHODIMP nsImapMailFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) { nsresult rv; bool endedOfflineDownload = false; nsImapAction imapAction = nsIImapUrl::nsImapTest; m_urlRunning = false; m_updatingFolder = false; nsCOMPtr session = do_GetService("@mozilla.org/messenger/services/session;1", &rv); NS_ENSURE_SUCCESS(rv, rv); if (aUrl) { nsCOMPtr imapUrl = do_QueryInterface(aUrl, &rv); NS_ENSURE_SUCCESS(rv, rv); bool downloadingForOfflineUse; imapUrl->GetStoreResultsOffline(&downloadingForOfflineUse); bool hasSemaphore = false; // if we have the folder locked, clear it. TestSemaphore(static_cast(this), &hasSemaphore); if (hasSemaphore) ReleaseSemaphore(static_cast(this)); if (downloadingForOfflineUse) { endedOfflineDownload = true; EndOfflineDownload(); } nsCOMPtr msgWindow; nsCOMPtr mailUrl = do_QueryInterface(aUrl); bool folderOpen = false; if (mailUrl) mailUrl->GetMsgWindow(getter_AddRefs(msgWindow)); if (session) session->IsFolderOpenInWindow(this, &folderOpen); #ifdef DEBUG_bienvenu printf("stop running url %s\n", aUrl->GetSpecOrDefault().get()); #endif if (imapUrl) { DisplayStatusMsg(imapUrl, EmptyString()); imapUrl->GetImapAction(&imapAction); if (imapAction == nsIImapUrl::nsImapMsgFetch || imapAction == nsIImapUrl::nsImapMsgDownloadForOffline) { ReleaseSemaphore(static_cast(this)); if (!endedOfflineDownload) EndOfflineDownload(); } // Notify move, copy or delete (online operations) // Not sure whether nsImapDeleteMsg is even used, deletes in all three // models use nsImapAddMsgFlags. nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier && m_copyState) { if (imapAction == nsIImapUrl::nsImapOnlineMove) { notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages, this, {}); } else if (imapAction == nsIImapUrl::nsImapOnlineCopy) { notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages, this, {}); } else if (imapAction == nsIImapUrl::nsImapDeleteMsg) { notifier->NotifyMsgsDeleted(m_copyState->m_messages); } } switch (imapAction) { case nsIImapUrl::nsImapDeleteMsg: case nsIImapUrl::nsImapOnlineMove: case nsIImapUrl::nsImapOnlineCopy: if (NS_SUCCEEDED(aExitCode)) { if (folderOpen) UpdateFolder(msgWindow); else UpdatePendingCounts(); } if (m_copyState) { nsCOMPtr srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv); if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp) { if (NS_SUCCEEDED(aExitCode)) { nsCOMPtr srcDB; if (srcFolder) rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); if (NS_SUCCEEDED(rv) && srcDB) { RefPtr msgTxn; nsTArray srcKeyArray; if (m_copyState->m_allowUndo) { msgTxn = m_copyState->m_undoMsgTxn; if (msgTxn) msgTxn->GetSrcKeyArray(srcKeyArray); } else { nsAutoCString messageIds; rv = BuildIdsAndKeyArray(m_copyState->m_messages, messageIds, srcKeyArray); NS_ENSURE_SUCCESS(rv, rv); } if (!ShowDeletedMessages()) { // We only reach here for same-server operations // (!m_copyState->m_isCrossServerOp in if above), so we can // assume that the src is also imap that uses offline // storage. DeleteStoreMessages(srcKeyArray, srcFolder); srcDB->DeleteMessages(srcKeyArray, nullptr); } else MarkMessagesImapDeleted(&srcKeyArray, true, srcDB); } srcFolder->EnableNotifications(allMessageCountNotifications, true); // even if we're showing deleted messages, // we still need to notify FE so it will show the imap deleted // flag srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted); // is there a way to see that we think we have new msgs? nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { bool showPreviewText; prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText); // if we're showing preview text, update ourselves if we got a // new unread message copied so that we can download the new // headers and have a chance to preview the msg bodies. if (!folderOpen && showPreviewText && m_copyState->m_unreadCount > 0 && !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) UpdateFolder(msgWindow); } } else { srcFolder->EnableNotifications(allMessageCountNotifications, true); srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed); } } if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn && // may be null from filters NS_SUCCEEDED( aExitCode)) // we should do this only if move/copy succeeds { nsCOMPtr txnMgr; m_copyState->m_msgWindow->GetTransactionManager( getter_AddRefs(txnMgr)); if (txnMgr) { RefPtr txn = m_copyState->m_undoMsgTxn; mozilla::DebugOnly rv2 = txnMgr->DoTransaction(txn); NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed"); } } // nsImapUrl can hold a pointer to our m_copyState, so force a // release here (see Bug 1586494). imapUrl->SetCopyState(nullptr); (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode); } // we're the dest folder of a move/copy - if we're not open in the ui, // then we should clear our nsMsgDatabase pointer. Otherwise, the db // would be open until the user selected it and then selected another // folder. but don't do this for the trash or inbox - we'll leave them // open if (!folderOpen && !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) SetMsgDatabase(nullptr); break; case nsIImapUrl::nsImapSubtractMsgFlags: { // this isn't really right - we'd like to know we were // deleting a message to start with, but it probably // won't do any harm. imapMessageFlagsType flags = 0; imapUrl->GetMsgFlags(&flags); // we need to subtract the delete flag in db only in case when we show // deleted msgs if (flags & kImapMsgDeletedFlag && ShowDeletedMessages()) { nsCOMPtr db; rv = GetMsgDatabase(getter_AddRefs(db)); if (NS_SUCCEEDED(rv) && db) { nsTArray keyArray; nsCString keyString; imapUrl->GetListOfMessageIds(keyString); ParseUidString(keyString.get(), keyArray); MarkMessagesImapDeleted(&keyArray, false, db); db->Commit(nsMsgDBCommitType::kLargeCommit); } } } break; case nsIImapUrl::nsImapAddMsgFlags: { imapMessageFlagsType flags = 0; imapUrl->GetMsgFlags(&flags); if (flags & kImapMsgDeletedFlag) { // we need to delete headers from db only when we don't show deleted // msgs if (!ShowDeletedMessages()) { nsCOMPtr db; rv = GetMsgDatabase(getter_AddRefs(db)); if (NS_SUCCEEDED(rv) && db) { nsTArray keyArray; nsCString keyString; imapUrl->GetListOfMessageIds(keyString); ParseUidString(keyString.get(), keyArray); // For pluggable stores that do not support compaction, we need // to delete the messages now. bool supportsCompaction = false; nsCOMPtr offlineStore; (void)GetMsgStore(getter_AddRefs(offlineStore)); if (offlineStore) offlineStore->GetSupportsCompaction(&supportsCompaction); nsTArray> msgHdrs; if (notifier || !supportsCompaction) { MsgGetHeadersFromKeys(db, keyArray, msgHdrs); } // Notify listeners of delete. if (notifier && !msgHdrs.IsEmpty()) { // XXX Currently, the DeleteMessages below gets executed twice // on deletes. Once in DeleteMessages, once here. The second // time, it silently fails to delete. This is why we're also // checking whether the array is empty. notifier->NotifyMsgsDeleted(msgHdrs); } if (!supportsCompaction && !msgHdrs.IsEmpty()) DeleteStoreMessages(msgHdrs); db->DeleteMessages(keyArray, nullptr); db->SetSummaryValid(true); db->Commit(nsMsgDBCommitType::kLargeCommit); } } } } break; case nsIImapUrl::nsImapAppendMsgFromFile: case nsIImapUrl::nsImapAppendDraftFromFile: if (m_copyState) { if (NS_SUCCEEDED(aExitCode)) { UpdatePendingCounts(); m_copyState->m_curIndex++; if (m_copyState->m_curIndex >= m_copyState->m_messages.Length()) { nsCOMPtr saveUrlListener = m_urlListener; if (folderOpen) { // This gives a way for the caller to get notified // when the UpdateFolder url is done. // (if the nsIMsgCopyServiceListener also implements // nsIUrlListener) if (m_copyState->m_listener) m_urlListener = do_QueryInterface(m_copyState->m_listener); } if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn) { nsCOMPtr txnMgr; m_copyState->m_msgWindow->GetTransactionManager( getter_AddRefs(txnMgr)); if (txnMgr) { RefPtr txn = m_copyState->m_undoMsgTxn; txnMgr->DoTransaction(txn); } } (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode); if (folderOpen || imapAction == nsIImapUrl::nsImapAppendDraftFromFile) { UpdateFolderWithListener(msgWindow, m_urlListener); m_urlListener = saveUrlListener; } } } else { // clear the copyState if copy has failed (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode); } } break; case nsIImapUrl::nsImapMoveFolderHierarchy: if (m_copyState) // delete folder gets here, but w/o an m_copyState { nsCOMPtr copyService = do_GetService( "@mozilla.org/messenger/messagecopyservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr srcFolder = do_QueryInterface(m_copyState->m_srcSupport); if (srcFolder) { copyService->NotifyCompletion(m_copyState->m_srcSupport, this, aExitCode); } m_copyState = nullptr; } break; case nsIImapUrl::nsImapRenameFolder: if (NS_FAILED(aExitCode)) { NotifyFolderEvent(kRenameCompleted); } break; case nsIImapUrl::nsImapDeleteAllMsgs: if (NS_SUCCEEDED(aExitCode)) { if (folderOpen) UpdateFolder(msgWindow); else { ChangeNumPendingTotalMessages(-mNumPendingTotalMessages); ChangeNumPendingUnread(-mNumPendingUnreadMessages); m_numServerUnseenMessages = 0; } } break; case nsIImapUrl::nsImapListFolder: if (NS_SUCCEEDED(aExitCode)) { // listing folder will open db; don't leave the db open. SetMsgDatabase(nullptr); if (!m_verifiedAsOnlineFolder) { // If folder is not verified, we remove it. nsCOMPtr parent; rv = GetParent(getter_AddRefs(parent)); if (NS_SUCCEEDED(rv) && parent) { nsCOMPtr imapParent = do_QueryInterface(parent); if (imapParent) this->RemoveLocalSelf(); } } } break; case nsIImapUrl::nsImapRefreshFolderUrls: // we finished getting an admin url for the folder. if (!m_adminUrl.IsEmpty()) FolderPrivileges(msgWindow); break; case nsIImapUrl::nsImapCreateFolder: if (NS_FAILED(aExitCode)) // if success notification already done { NotifyFolderEvent(kFolderCreateFailed); } break; case nsIImapUrl::nsImapSubscribe: if (NS_SUCCEEDED(aExitCode) && msgWindow) { nsCString canonicalFolderName; imapUrl->CreateCanonicalSourceFolderPathString( getter_Copies(canonicalFolderName)); nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && rootFolder) { nsCOMPtr imapRoot = do_QueryInterface(rootFolder); if (imapRoot) { nsCOMPtr foundFolder; rv = imapRoot->FindOnlineSubFolder(canonicalFolderName, getter_AddRefs(foundFolder)); if (NS_SUCCEEDED(rv) && foundFolder) { nsCString uri; nsCOMPtr msgFolder = do_QueryInterface(foundFolder); if (msgFolder) { nsCOMPtr obsServ = mozilla::services::GetObserverService(); obsServ->NotifyObservers(msgFolder, "folder-subscribed", nullptr); } } } } } break; case nsIImapUrl::nsImapExpungeFolder: break; default: break; } } // give base class a chance to send folder loaded notification... rv = nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); } // if we're not running a url, we must not be getting new mail. SetGettingNewMessages(false); // If we're planning to inform another listener then do that now. // Some folder methods can take a listener to inform when the operation // is complete e.g. UpdateFolderWithListener(), DownloadAllForOffline(), // GetNewMessages(). That listener is stashed in m_urlListener. if (m_urlListener) { nsCOMPtr saveListener = m_urlListener; m_urlListener = nullptr; saveListener->OnStopRunningUrl(aUrl, aExitCode); } return rv; } void nsImapMailFolder::UpdatePendingCounts() { if (m_copyState) { int32_t delta = m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_messages.Length(); if (!m_copyState->m_selectedState && m_copyState->m_messages.IsEmpty()) { // special case from CopyFileMessage(): // - copied a single message in from a file // - no previously-existing messages are involved delta = 1; } ChangePendingTotal(delta); // count the moves that were unread int numUnread = m_copyState->m_unreadCount; if (numUnread) { m_numServerUnseenMessages += numUnread; // adjust last status count by this delta. ChangeNumPendingUnread(numUnread); } SummaryChanged(); } } NS_IMETHODIMP nsImapMailFolder::ClearFolderRights() { SetFolderNeedsACLListed(false); delete m_folderACL; m_folderACL = new nsMsgIMAPFolderACL(this); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::AddFolderRights(const nsACString& userName, const nsACString& rights) { SetFolderNeedsACLListed(false); GetFolderACL()->SetFolderRightsForUser(userName, rights); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::RefreshFolderRights() { if (GetFolderACL()->GetIsFolderShared()) SetFlag(nsMsgFolderFlags::PersonalShared); else ClearFlag(nsMsgFolderFlags::PersonalShared); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetCopyResponseUid(const char* msgIdString, nsIImapUrl* aUrl) { // CopyMessages() only nsresult rv = NS_OK; RefPtr msgTxn; nsCOMPtr copyState; if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) { nsCOMPtr mailCopyState = do_QueryInterface(copyState, &rv); if (NS_FAILED(rv)) return rv; if (mailCopyState->m_undoMsgTxn) msgTxn = mailCopyState->m_undoMsgTxn; } else if (aUrl && m_pendingOfflineMoves.Length()) { nsCString urlSourceMsgIds, undoTxnSourceMsgIds; aUrl->GetListOfMessageIds(urlSourceMsgIds); RefPtr imapUndo = m_pendingOfflineMoves[0]; if (imapUndo) { imapUndo->GetSrcMsgIds(undoTxnSourceMsgIds); if (undoTxnSourceMsgIds.Equals(urlSourceMsgIds)) msgTxn = imapUndo; // ### we should handle batched moves, but lets keep it simple for a2. m_pendingOfflineMoves.Clear(); } } if (msgTxn) msgTxn->SetCopyResponseUid(msgIdString); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::StartMessage(nsIMsgMailNewsUrl* aUrl) { nsCOMPtr imapUrl(do_QueryInterface(aUrl)); nsCOMPtr copyState; NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE); imapUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) { nsCOMPtr listener = do_QueryInterface(copyState); if (listener) listener->StartMessage(); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl* aUrl, nsMsgKey uidOfMessage) { nsCOMPtr imapUrl(do_QueryInterface(aUrl)); nsCOMPtr copyState; NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE); imapUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) { nsCOMPtr listener = do_QueryInterface(copyState); if (listener) listener->EndMessage(uidOfMessage); } return NS_OK; } #define WHITESPACE " \015\012" // token delimiter NS_IMETHODIMP nsImapMailFolder::NotifySearchHit(nsIMsgMailNewsUrl* aUrl, const char* searchHitLine) { NS_ENSURE_ARG_POINTER(aUrl); nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); // expect search results in the form of "* SEARCH ..." // expect search results in the form of "* SEARCH ..." nsCString tokenString(searchHitLine); char* currentPosition = PL_strcasestr(tokenString.get(), "SEARCH"); if (currentPosition) { currentPosition += strlen("SEARCH"); bool shownUpdateAlert = false; char* hitUidToken = NS_strtok(WHITESPACE, ¤tPosition); while (hitUidToken) { long naturalLong; // %l is 64 bits on OSF1 sscanf(hitUidToken, "%ld", &naturalLong); nsMsgKey hitUid = (nsMsgKey)naturalLong; nsCOMPtr hitHeader; rv = mDatabase->GetMsgHdrForKey(hitUid, getter_AddRefs(hitHeader)); if (NS_SUCCEEDED(rv) && hitHeader) { nsCOMPtr searchSession; nsCOMPtr searchAdapter; aUrl->GetSearchSession(getter_AddRefs(searchSession)); if (searchSession) { searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter)); if (searchAdapter) searchAdapter->AddResultElement(hitHeader); } } else if (!shownUpdateAlert) { } hitUidToken = NS_strtok(WHITESPACE, ¤tPosition); } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetAppendMsgUid(nsMsgKey aKey, nsIImapUrl* aUrl) { nsresult rv; nsCOMPtr copyState; if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) { nsCOMPtr mailCopyState = do_QueryInterface(copyState, &rv); if (NS_FAILED(rv)) return rv; if (mailCopyState->m_undoMsgTxn) // CopyMessages() { RefPtr msgTxn; msgTxn = mailCopyState->m_undoMsgTxn; msgTxn->AddDstKey(aKey); } else if (mailCopyState->m_listener) // CopyFileMessage(); // Draft/Template goes here { mailCopyState->m_appendUID = aKey; mailCopyState->m_listener->SetMessageKey(aKey); } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetMessageId(nsIImapUrl* aUrl, nsACString& messageId) { nsresult rv = NS_OK; nsCOMPtr copyState; if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) { nsCOMPtr mailCopyState = do_QueryInterface(copyState, &rv); if (NS_FAILED(rv)) return rv; if (mailCopyState->m_listener) rv = mailCopyState->m_listener->GetMessageId(messageId); } if (NS_SUCCEEDED(rv) && messageId.Length() > 0) { if (messageId.First() == '<') messageId.Cut(0, 1); if (messageId.Last() == '>') messageId.SetLength(messageId.Length() - 1); } return rv; } NS_IMETHODIMP nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol) { nsCOMPtr msgWindow; // we might need this for the filter plugins. if (mBackupDatabase) RemoveBackupMsgDatabase(); SetSizeOnDisk(mFolderSize); int32_t numNewBiffMsgs = 0; if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs); bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves(); PlaybackCoalescedOperations(); if (aProtocol) { // check if we should download message bodies because it's the inbox and // the server is specified as one where where we download msg bodies // automatically. Or if we autosyncing all offline folders. nsCOMPtr imapServer; GetImapIncomingServer(getter_AddRefs(imapServer)); bool autoDownloadNewHeaders = false; bool autoSyncOfflineStores = false; if (imapServer) { imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores); imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders); if (m_filterListRequiresBody) autoDownloadNewHeaders = true; } bool notifiedBodies = false; if (m_downloadingFolderForOfflineUse || autoSyncOfflineStores || autoDownloadNewHeaders) { nsTArray keysToDownload; GetBodysToDownload(&keysToDownload); if (!keysToDownload.IsEmpty() && (m_downloadingFolderForOfflineUse || autoDownloadNewHeaders)) { // this is the case when DownloadAllForOffline is called. notifiedBodies = true; aProtocol->NotifyBodysToDownload(keysToDownload); } else { // create auto-sync state object lazily InitAutoSyncState(); if (MOZ_LOG_TEST(gAutoSyncLog, mozilla::LogLevel::Debug)) { int32_t flags = 0; GetFlags((uint32_t*)&flags); nsString folderName; GetName(folderName); nsCString utfLeafName; CopyUTF16toUTF8(folderName, utfLeafName); MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("%s: foldername=%s, flags=0x%X, " "isOffline=%s, nsMsgFolderFlags::Offline=0x%X", __func__, utfLeafName.get(), flags, (flags & nsMsgFolderFlags::Offline) ? "true" : "false", nsMsgFolderFlags::Offline)); MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("%s: created autosync obj, have keys to download=%s", __func__, keysToDownload.IsEmpty() ? "false" : "true")); } // make enough room for new downloads m_autoSyncStateObj->ManageStorageSpace(); // currently a no-op m_autoSyncStateObj->SetServerCounts( m_numServerTotalMessages, m_numServerRecentMessages, m_numServerUnseenMessages, m_nextUID); m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload); } } if (!notifiedBodies) { nsTArray noBodies; aProtocol->NotifyBodysToDownload(noBodies); } nsCOMPtr runningUri; aProtocol->GetRunningUrl(getter_AddRefs(runningUri)); if (runningUri) { nsCOMPtr mailnewsUrl = do_QueryInterface(runningUri); if (mailnewsUrl) mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); } } // delay calling plugins if filter application is also delayed if (!m_filterListRequiresBody) { bool filtersRun; CallFilterPlugins(msgWindow, &filtersRun); if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 && (!pendingMoves || !ShowPreviewText())) { // If we are performing biff for this folder, tell the // stand-alone biff about the new high water mark // We must ensure that the server knows that we are performing biff. // Otherwise the stand-alone biff won't fire. nsCOMPtr server; if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) server->SetPerformingBiff(true); SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail); if (server) server->SetPerformingBiff(false); m_performingBiff = false; } if (m_filterList) (void)m_filterList->FlushLogIfNecessary(); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetBiffStateAndUpdate(nsMsgBiffState biffState) { SetBiffState(biffState); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetUidValidity(int32_t* uidValidity) { NS_ENSURE_ARG(uidValidity); if ((int32_t)m_uidValidity == kUidUnknown) { nsCOMPtr db; nsCOMPtr dbFolderInfo; (void)GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); if (db) db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (dbFolderInfo) dbFolderInfo->GetImapUidValidity((int32_t*)&m_uidValidity); } *uidValidity = m_uidValidity; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetUidValidity(int32_t uidValidity) { m_uidValidity = uidValidity; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::FillInFolderProps(nsIMsgImapFolderProps* aFolderProps) { NS_ENSURE_ARG(aFolderProps); const char* folderTypeStringID; const char* folderTypeDescStringID = nullptr; const char* folderQuotaStatusStringID; nsString folderType; nsString folderTypeDesc; nsString folderQuotaStatusDesc; nsCOMPtr bundle; nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); // if for some bizarre reason this fails, we'll still fall through to the // normal sharing code if (NS_SUCCEEDED(rv)) { // get the latest committed imap capabilities bit mask. eIMAPCapabilityFlags capability = kCapabilityUndefined; imapServer->GetCapability(&capability); bool haveACL = capability & kACLCapability; bool haveQuota = capability & kQuotaCapability; // Figure out what to display in the Quota tab of the folder properties. // Does the server support quotas? This depends on the latest imap // CAPABILITY response. if (haveQuota) { // Have quota capability. Have we asked the server for quota information? if (m_folderQuotaCommandIssued) { // Has the server replied with all the quota info? if (m_folderQuotaDataIsValid) { if (!m_folderQuota.IsEmpty()) { // If so, set quota data to show in the quota tab folderQuotaStatusStringID = nullptr; aFolderProps->SetQuotaData(m_folderQuota); } else { // The server reported no quota limits on this folder. folderQuotaStatusStringID = "imapQuotaStatusNoQuota2"; } } else { // The getquotaroot command was sent to the server but the complete // response was not yet received when the folder properties were // requested. This is rare. Request the folder properties again to // obtain the quota data. folderQuotaStatusStringID = "imapQuotaStatusInProgress"; } } else { // The folder is not open, so no quota information is available folderQuotaStatusStringID = "imapQuotaStatusFolderNotOpen"; } } else { // Either the server doesn't support quotas, or we don't know if it does // (e.g., because we don't have a connection yet). If the latter, we fall // back to saying that no information is available because the folder is // not yet open. folderQuotaStatusStringID = (capability == kCapabilityUndefined) ? "imapQuotaStatusFolderNotOpen" : "imapQuotaStatusNotSupported"; } if (!folderQuotaStatusStringID) { // Display quota data aFolderProps->ShowQuotaData(true); } else { // Hide quota data and show reason why it is not available aFolderProps->ShowQuotaData(false); rv = IMAPGetStringByName(folderQuotaStatusStringID, getter_Copies(folderQuotaStatusDesc)); if (NS_SUCCEEDED(rv)) aFolderProps->SetQuotaStatus(folderQuotaStatusDesc); } // See if the server supports ACL. // If not, just set the folder description to a string that says // the server doesn't support sharing, and return. if (!haveACL) { rv = IMAPGetStringByName("imapServerDoesntSupportAcl", getter_Copies(folderTypeDesc)); if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderTypeDescription(folderTypeDesc); aFolderProps->ServerDoesntSupportACL(); return NS_OK; } } if (mFlags & nsMsgFolderFlags::ImapPublic) { folderTypeStringID = "imapPublicFolderTypeName"; folderTypeDescStringID = "imapPublicFolderTypeDescription"; } else if (mFlags & nsMsgFolderFlags::ImapOtherUser) { folderTypeStringID = "imapOtherUsersFolderTypeName"; nsCString owner; nsString uniOwner; GetFolderOwnerUserName(owner); if (owner.IsEmpty()) { IMAPGetStringByName(folderTypeStringID, getter_Copies(uniOwner)); // Another user's folder, for which we couldn't find an owner name NS_ASSERTION(false, "couldn't get owner name for other user's folder"); } else { CopyUTF8toUTF16(owner, uniOwner); } AutoTArray params = {uniOwner}; bundle->FormatStringFromName("imapOtherUsersFolderTypeDescription", params, folderTypeDesc); } else if (GetFolderACL()->GetIsFolderShared()) { folderTypeStringID = "imapPersonalSharedFolderTypeName"; folderTypeDescStringID = "imapPersonalSharedFolderTypeDescription"; } else { folderTypeStringID = "imapPersonalSharedFolderTypeName"; folderTypeDescStringID = "imapPersonalFolderTypeDescription"; } rv = IMAPGetStringByName(folderTypeStringID, getter_Copies(folderType)); if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderType(folderType); if (folderTypeDesc.IsEmpty() && folderTypeDescStringID) IMAPGetStringByName(folderTypeDescStringID, getter_Copies(folderTypeDesc)); if (!folderTypeDesc.IsEmpty()) aFolderProps->SetFolderTypeDescription(folderTypeDesc); nsString rightsString; rv = CreateACLRightsStringForFolder(rightsString); if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderPermissions(rightsString); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetAclFlags(uint32_t aclFlags) { nsresult rv = NS_OK; if (m_aclFlags != aclFlags) { nsCOMPtr dbFolderInfo; bool dbWasOpen = (mDatabase != nullptr); rv = GetDatabase(); m_aclFlags = aclFlags; if (mDatabase) { rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (NS_SUCCEEDED(rv) && dbFolderInfo) dbFolderInfo->SetUint32Property("aclFlags", aclFlags); // if setting the acl flags caused us to open the db, release the ref // because on startup, we might get acl on all folders,which will // leave a lot of db's open. if (!dbWasOpen) { mDatabase->Close(true /* commit changes */); mDatabase = nullptr; } } } return rv; } NS_IMETHODIMP nsImapMailFolder::GetAclFlags(uint32_t* aclFlags) { NS_ENSURE_ARG_POINTER(aclFlags); nsresult rv; ReadDBFolderInfo(false); // update cache first. if (m_aclFlags == kAclInvalid) // -1 means invalid value, so get it from db. { nsCOMPtr dbFolderInfo; bool dbWasOpen = (mDatabase != nullptr); rv = GetDatabase(); if (mDatabase) { rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (NS_SUCCEEDED(rv) && dbFolderInfo) { rv = dbFolderInfo->GetUint32Property("aclFlags", 0, aclFlags); m_aclFlags = *aclFlags; } // if getting the acl flags caused us to open the db, release the ref // because on startup, we might get acl on all folders,which will // leave a lot of db's open. if (!dbWasOpen) { mDatabase->Close(true /* commit changes */); mDatabase = nullptr; } } } else *aclFlags = m_aclFlags; return NS_OK; } nsresult nsImapMailFolder::SetSupportedUserFlags(uint32_t userFlags) { nsCOMPtr dbFolderInfo; nsresult rv = GetDatabase(); m_supportedUserFlags = userFlags; if (mDatabase) { rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (NS_SUCCEEDED(rv) && dbFolderInfo) dbFolderInfo->SetUint32Property("imapFlags", userFlags); } return rv; } nsresult nsImapMailFolder::GetSupportedUserFlags(uint32_t* userFlags) { NS_ENSURE_ARG_POINTER(userFlags); nsresult rv = NS_OK; ReadDBFolderInfo(false); // update cache first. if (m_supportedUserFlags == 0) // 0 means invalid value, so get it from db. { nsCOMPtr dbFolderInfo; rv = GetDatabase(); if (mDatabase) { rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); if (NS_SUCCEEDED(rv) && dbFolderInfo) { rv = dbFolderInfo->GetUint32Property("imapFlags", 0, userFlags); m_supportedUserFlags = *userFlags; } } } else *userFlags = m_supportedUserFlags; return rv; } NS_IMETHODIMP nsImapMailFolder::GetCanOpenFolder(bool* aBool) { NS_ENSURE_ARG_POINTER(aBool); bool noSelect; GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect); *aBool = (noSelect) ? false : GetFolderACL()->GetCanIReadFolder(); return NS_OK; } ///////// nsMsgIMAPFolderACL class /////////////////////////////// // This string is defined in the ACL RFC to be "anyone" #define IMAP_ACL_ANYONE_STRING "anyone" nsMsgIMAPFolderACL::nsMsgIMAPFolderACL(nsImapMailFolder* folder) : m_rightsHash(24) { NS_ASSERTION(folder, "need folder"); m_folder = folder; m_aclCount = 0; BuildInitialACLFromCache(); } nsMsgIMAPFolderACL::~nsMsgIMAPFolderACL() {} // We cache most of our own rights in the MSG_FOLDER_PREF_* flags void nsMsgIMAPFolderACL::BuildInitialACLFromCache() { nsAutoCString myrights; uint32_t startingFlags; m_folder->GetAclFlags(&startingFlags); if (startingFlags & IMAP_ACL_READ_FLAG) myrights += "r"; if (startingFlags & IMAP_ACL_STORE_SEEN_FLAG) myrights += "s"; if (startingFlags & IMAP_ACL_WRITE_FLAG) myrights += "w"; if (startingFlags & IMAP_ACL_INSERT_FLAG) myrights += "i"; if (startingFlags & IMAP_ACL_POST_FLAG) myrights += "p"; if (startingFlags & IMAP_ACL_CREATE_SUBFOLDER_FLAG) myrights += "c"; if (startingFlags & IMAP_ACL_DELETE_FLAG) myrights += "dt"; if (startingFlags & IMAP_ACL_ADMINISTER_FLAG) myrights += "a"; if (startingFlags & IMAP_ACL_EXPUNGE_FLAG) myrights += "e"; if (!myrights.IsEmpty()) SetFolderRightsForUser(EmptyCString(), myrights); } void nsMsgIMAPFolderACL::UpdateACLCache() { uint32_t startingFlags = 0; m_folder->GetAclFlags(&startingFlags); if (GetCanIReadFolder()) startingFlags |= IMAP_ACL_READ_FLAG; else startingFlags &= ~IMAP_ACL_READ_FLAG; if (GetCanIStoreSeenInFolder()) startingFlags |= IMAP_ACL_STORE_SEEN_FLAG; else startingFlags &= ~IMAP_ACL_STORE_SEEN_FLAG; if (GetCanIWriteFolder()) startingFlags |= IMAP_ACL_WRITE_FLAG; else startingFlags &= ~IMAP_ACL_WRITE_FLAG; if (GetCanIInsertInFolder()) startingFlags |= IMAP_ACL_INSERT_FLAG; else startingFlags &= ~IMAP_ACL_INSERT_FLAG; if (GetCanIPostToFolder()) startingFlags |= IMAP_ACL_POST_FLAG; else startingFlags &= ~IMAP_ACL_POST_FLAG; if (GetCanICreateSubfolder()) startingFlags |= IMAP_ACL_CREATE_SUBFOLDER_FLAG; else startingFlags &= ~IMAP_ACL_CREATE_SUBFOLDER_FLAG; if (GetCanIDeleteInFolder()) startingFlags |= IMAP_ACL_DELETE_FLAG; else startingFlags &= ~IMAP_ACL_DELETE_FLAG; if (GetCanIAdministerFolder()) startingFlags |= IMAP_ACL_ADMINISTER_FLAG; else startingFlags &= ~IMAP_ACL_ADMINISTER_FLAG; if (GetCanIExpungeFolder()) startingFlags |= IMAP_ACL_EXPUNGE_FLAG; else startingFlags &= ~IMAP_ACL_EXPUNGE_FLAG; m_folder->SetAclFlags(startingFlags); } bool nsMsgIMAPFolderACL::SetFolderRightsForUser(const nsACString& userName, const nsACString& rights) { nsCString myUserName; nsCOMPtr server; nsresult rv = m_folder->GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, false); server->GetUsername(myUserName); nsAutoCString ourUserName; if (userName.IsEmpty()) ourUserName.Assign(myUserName); else ourUserName.Assign(userName); if (ourUserName.IsEmpty()) return false; ToLowerCase(ourUserName); nsCString oldValue = m_rightsHash.Get(ourUserName); if (!oldValue.IsEmpty()) { m_rightsHash.Remove(ourUserName); m_aclCount--; NS_ASSERTION(m_aclCount >= 0, "acl count can't go negative"); } m_aclCount++; m_rightsHash.InsertOrUpdate(ourUserName, PromiseFlatCString(rights)); if (myUserName.Equals(ourUserName) || ourUserName.EqualsLiteral(IMAP_ACL_ANYONE_STRING)) // if this is setting an ACL for me, cache it in the folder pref flags UpdateACLCache(); return true; } NS_IMETHODIMP nsImapMailFolder::GetOtherUsersWithAccess( nsIUTF8StringEnumerator** aResult) { return GetFolderACL()->GetOtherUsers(aResult); } nsresult nsMsgIMAPFolderACL::GetOtherUsers(nsIUTF8StringEnumerator** aResult) { nsCString myUserName; nsCOMPtr server; nsresult rv = m_folder->GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); server->GetUsername(myUserName); // We need to filter out myUserName from m_rightsHash. nsTArray* resultArray = new nsTArray; for (auto iter = m_rightsHash.Iter(); !iter.Done(); iter.Next()) { if (!iter.Key().Equals(myUserName)) resultArray->AppendElement(iter.Key()); } // enumerator will free resultArray return NS_NewAdoptingUTF8StringEnumerator(aResult, resultArray); } nsresult nsImapMailFolder::GetPermissionsForUser(const nsACString& otherUser, nsACString& aResult) { nsCString str; nsresult rv = GetFolderACL()->GetRightsStringForUser(otherUser, str); NS_ENSURE_SUCCESS(rv, rv); aResult = str; return NS_OK; } nsresult nsMsgIMAPFolderACL::GetRightsStringForUser( const nsACString& inUserName, nsCString& rights) { nsCString userName; userName.Assign(inUserName); if (userName.IsEmpty()) { nsCOMPtr server; nsresult rv = m_folder->GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); // we need the real user name to match with what the imap server returns // in the acl response. server->GetUsername(userName); } ToLowerCase(userName); rights = m_rightsHash.Get(userName); return NS_OK; } // First looks for individual user; then looks for 'anyone' if the user isn't // found. Returns defaultIfNotFound, if neither are found. bool nsMsgIMAPFolderACL::GetFlagSetInRightsForUser(const nsACString& userName, char flag, bool defaultIfNotFound) { nsCString flags; nsresult rv = GetRightsStringForUser(userName, flags); NS_ENSURE_SUCCESS(rv, defaultIfNotFound); if (flags.IsEmpty()) { nsCString anyoneFlags; GetRightsStringForUser(nsLiteralCString(IMAP_ACL_ANYONE_STRING), anyoneFlags); if (anyoneFlags.IsEmpty()) return defaultIfNotFound; return (anyoneFlags.FindChar(flag) != kNotFound); } return (flags.FindChar(flag) != kNotFound); } bool nsMsgIMAPFolderACL::GetCanUserLookupFolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'l', false); } bool nsMsgIMAPFolderACL::GetCanUserReadFolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'r', false); } bool nsMsgIMAPFolderACL::GetCanUserStoreSeenInFolder( const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 's', false); } bool nsMsgIMAPFolderACL::GetCanUserWriteFolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'w', false); } bool nsMsgIMAPFolderACL::GetCanUserInsertInFolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'i', false); } bool nsMsgIMAPFolderACL::GetCanUserPostToFolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'p', false); } bool nsMsgIMAPFolderACL::GetCanUserCreateSubfolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'c', false); } bool nsMsgIMAPFolderACL::GetCanUserDeleteInFolder(const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'd', false) || GetFlagSetInRightsForUser(userName, 't', false); } bool nsMsgIMAPFolderACL::GetCanUserAdministerFolder( const nsACString& userName) { return GetFlagSetInRightsForUser(userName, 'a', false); } bool nsMsgIMAPFolderACL::GetCanILookupFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'l', true); } bool nsMsgIMAPFolderACL::GetCanIReadFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'r', true); } bool nsMsgIMAPFolderACL::GetCanIStoreSeenInFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 's', true); } bool nsMsgIMAPFolderACL::GetCanIWriteFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'w', true); } bool nsMsgIMAPFolderACL::GetCanIInsertInFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'i', true); } bool nsMsgIMAPFolderACL::GetCanIPostToFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'p', true); } bool nsMsgIMAPFolderACL::GetCanICreateSubfolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'c', true); } bool nsMsgIMAPFolderACL::GetCanIDeleteInFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'd', true) || GetFlagSetInRightsForUser(EmptyCString(), 't', true); } bool nsMsgIMAPFolderACL::GetCanIAdministerFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'a', true); } bool nsMsgIMAPFolderACL::GetCanIExpungeFolder() { return GetFlagSetInRightsForUser(EmptyCString(), 'e', true) || GetFlagSetInRightsForUser(EmptyCString(), 'd', true); } // We use this to see if the ACLs think a folder is shared or not. // We will define "Shared" in 5.0 to mean: // At least one user other than the currently authenticated user has at least // one explicitly-listed ACL right on that folder. bool nsMsgIMAPFolderACL::GetIsFolderShared() { // If we have more than one ACL count for this folder, which means that // someone other than ourself has rights on it, then it is "shared." if (m_aclCount > 1) return true; // Or, if "anyone" has rights to it, it is shared. nsCString anyonesRights = m_rightsHash.Get(nsLiteralCString(IMAP_ACL_ANYONE_STRING)); return (!anyonesRights.IsEmpty()); } bool nsMsgIMAPFolderACL::GetDoIHaveFullRightsForFolder() { return (GetCanIReadFolder() && GetCanIWriteFolder() && GetCanIInsertInFolder() && GetCanIAdministerFolder() && GetCanICreateSubfolder() && GetCanIDeleteInFolder() && GetCanILookupFolder() && GetCanIStoreSeenInFolder() && GetCanIExpungeFolder() && GetCanIPostToFolder()); } // Returns a newly allocated string describing these rights nsresult nsMsgIMAPFolderACL::CreateACLRightsString(nsAString& aRightsString) { nsString curRight; nsCOMPtr bundle; nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); if (GetDoIHaveFullRightsForFolder()) { nsAutoString result; rv = bundle->GetStringFromName("imapAclFullRights", result); aRightsString.Assign(result); return rv; } if (GetCanIReadFolder()) { bundle->GetStringFromName("imapAclReadRight", curRight); aRightsString.Append(curRight); } if (GetCanIWriteFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclWriteRight", curRight); aRightsString.Append(curRight); } if (GetCanIInsertInFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclInsertRight", curRight); aRightsString.Append(curRight); } if (GetCanILookupFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclLookupRight", curRight); aRightsString.Append(curRight); } if (GetCanIStoreSeenInFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclSeenRight", curRight); aRightsString.Append(curRight); } if (GetCanIDeleteInFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclDeleteRight", curRight); aRightsString.Append(curRight); } if (GetCanIExpungeFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclExpungeRight", curRight); aRightsString.Append(curRight); } if (GetCanICreateSubfolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclCreateRight", curRight); aRightsString.Append(curRight); } if (GetCanIPostToFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclPostRight", curRight); aRightsString.Append(curRight); } if (GetCanIAdministerFolder()) { if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", "); bundle->GetStringFromName("imapAclAdministerRight", curRight); aRightsString.Append(curRight); } return rv; } NS_IMETHODIMP nsImapMailFolder::GetFilePath(nsIFile** aPathName) { // this will return a copy of mPath, which is what we want. // this will also initialize mPath using parseURI if it isn't already done return nsMsgDBFolder::GetFilePath(aPathName); } NS_IMETHODIMP nsImapMailFolder::SetFilePath(nsIFile* aPathName) { return nsMsgDBFolder::SetFilePath( aPathName); // call base class so mPath will get set } nsresult nsImapMailFolder::DisplayStatusMsg(nsIImapUrl* aImapUrl, const nsAString& msg) { nsCOMPtr mockChannel; aImapUrl->GetMockChannel(getter_AddRefs(mockChannel)); if (mockChannel) { nsCOMPtr progressSink; mockChannel->GetProgressEventSink(getter_AddRefs(progressSink)); if (progressSink) { progressSink->OnStatus(mockChannel, NS_OK, PromiseFlatString(msg).get()); // XXX i18n message } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::ProgressStatusString(nsIImapProtocol* aProtocol, const char* aMsgName, const char16_t* extraInfo) { nsString progressMsg; nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) { nsCOMPtr serverSink = do_QueryInterface(server); if (serverSink) serverSink->GetImapStringByName(aMsgName, progressMsg); } if (progressMsg.IsEmpty()) IMAPGetStringByName(aMsgName, getter_Copies(progressMsg)); if (aProtocol && !progressMsg.IsEmpty()) { nsCOMPtr imapUrl; aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl)); if (imapUrl) { if (extraInfo) { nsString printfString; nsTextFormatter::ssprintf(printfString, progressMsg.get(), extraInfo); progressMsg = printfString; } DisplayStatusMsg(imapUrl, progressMsg); } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol, nsACString const& aFmtStringName, nsAString const& aMailboxName, int64_t aCurrentProgress, int64_t aMaxProgress) { if (aProtocol) { nsCOMPtr imapUrl; aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl)); if (imapUrl) { nsCOMPtr mockChannel; imapUrl->GetMockChannel(getter_AddRefs(mockChannel)); if (mockChannel) { nsCOMPtr progressSink; mockChannel->GetProgressEventSink(getter_AddRefs(progressSink)); if (progressSink) { progressSink->OnProgress(mockChannel, aCurrentProgress, aMaxProgress); if (!aFmtStringName.IsEmpty()) { // There's a progress message to format (the progress messages are // all localized and expect three params). nsAutoString current; current.AppendInt(aCurrentProgress); nsAutoString expected; expected.AppendInt(aMaxProgress); nsAutoString mailbox(aMailboxName); AutoTArray params = {current, expected, mailbox}; nsCOMPtr bundle; nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsString progressText; rv = bundle->FormatStringFromName( PromiseFlatCString(aFmtStringName).get(), params, progressText); NS_ENSURE_SUCCESS(rv, rv); if (!progressText.IsEmpty()) { progressSink->OnStatus(mockChannel, NS_OK, progressText.get()); } } } } } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::CopyNextStreamMessage(bool copySucceeded, nsISupports* copyState) { // if copy has failed it could be either user interrupted it or for some other // reason don't do any subsequent copies or delete src messages if it is move if (!copySucceeded) return NS_OK; nsresult rv; nsCOMPtr mailCopyState = do_QueryInterface(copyState, &rv); if (NS_FAILED(rv)) { MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("QI copyState failed: %" PRIx32, static_cast(rv))); return rv; // this can fail... } if (!mailCopyState->m_streamCopy) return NS_OK; uint32_t idx = mailCopyState->m_curIndex; if (mailCopyState->m_isMove && idx) { nsCOMPtr srcFolder( do_QueryInterface(mailCopyState->m_srcSupport, &rv)); if (NS_SUCCEEDED(rv) && srcFolder) { // Create "array" of one message header to delete idx--; if (idx < mailCopyState->m_messages.Length()) { RefPtr msg = mailCopyState->m_messages[idx]; srcFolder->DeleteMessages({msg}, nullptr, true, true, nullptr, false); } } } if (mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) { MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyNextStreamMessage: %s %u of %u", mailCopyState->m_isMove ? "Moving" : "Copying", mailCopyState->m_curIndex, (uint32_t)mailCopyState->m_messages.Length())); nsIMsgDBHdr* message = mailCopyState->m_messages[mailCopyState->m_curIndex]; bool isRead; message->GetIsRead(&isRead); mailCopyState->m_unreadCount = (isRead) ? 0 : 1; rv = CopyStreamMessage(message, this, mailCopyState->m_msgWindow, mailCopyState->m_isMove); } else { // Notify of move/copy completion in case we have some source headers nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier && !mailCopyState->m_messages.IsEmpty()) { notifier->NotifyMsgsMoveCopyCompleted( mailCopyState->m_isMove, mailCopyState->m_messages, this, {}); } if (mailCopyState->m_isMove) { nsCOMPtr srcFolder( do_QueryInterface(mailCopyState->m_srcSupport, &rv)); if (NS_SUCCEEDED(rv) && srcFolder) { // we want to send this notification now that the source messages have // been deleted. nsCOMPtr popFolder(do_QueryInterface(srcFolder)); if (popFolder) // needed if move pop->imap to notify FE srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted); } } } if (NS_FAILED(rv)) (void)OnCopyCompleted(mailCopyState->m_srcSupport, rv); return rv; } NS_IMETHODIMP nsImapMailFolder::SetUrlState(nsIImapProtocol* aProtocol, nsIMsgMailNewsUrl* aUrl, bool isRunning, bool aSuspend, nsresult statusCode) { // If we have no path, then the folder has been shutdown, and there's // no point in doing anything... if (!mPath) return NS_OK; if (!isRunning) { ProgressStatusString(aProtocol, "imapDone", nullptr); m_urlRunning = false; // if no protocol, then we're reading from the mem or disk cache // and we don't want to end the offline download just yet. if (aProtocol) { EndOfflineDownload(); m_downloadingFolderForOfflineUse = false; } nsCOMPtr imapUrl(do_QueryInterface(aUrl)); if (imapUrl) { nsImapAction imapAction; imapUrl->GetImapAction(&imapAction); // if the server doesn't support copyUID, then SetCopyResponseUid won't // get called, so we need to clear m_pendingOfflineMoves when the online // move operation has finished. if (imapAction == nsIImapUrl::nsImapOnlineMove) m_pendingOfflineMoves.Clear(); } } if (aUrl && !aSuspend) return aUrl->SetUrlState(isRunning, statusCode); return statusCode; } // used when copying from local mail folder, or other imap server) nsresult nsImapMailFolder::CopyMessagesWithStream( nsIMsgFolder* srcFolder, nsTArray> const& messages, bool isMove, bool isCrossServerOp, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener, bool allowUndo) { NS_ENSURE_ARG_POINTER(srcFolder); nsresult rv; rv = InitCopyState(srcFolder, messages, isMove, false, isCrossServerOp, 0, EmptyCString(), listener, msgWindow, allowUndo); if (NS_FAILED(rv)) return rv; m_copyState->m_streamCopy = true; // ** jt - needs to create server to server move/copy undo msg txn if (m_copyState->m_allowUndo) { nsAutoCString messageIds; nsTArray srcKeyArray; rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray); RefPtr undoMsgTxn = new nsImapMoveCopyMsgTxn; if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(srcFolder, &srcKeyArray, messageIds.get(), this, true, isMove))) return NS_ERROR_OUT_OF_MEMORY; if (isMove) { if (mFlags & nsMsgFolderFlags::Trash) undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); else undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg); } else undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg); m_copyState->m_undoMsgTxn = undoMsgTxn; } if (NS_SUCCEEDED(rv)) CopyStreamMessage(messages[0], this, msgWindow, isMove); return rv; // we are clearing copy state in CopyMessages on failure } nsresult nsImapMailFolder::GetClearedOriginalOp( nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp, nsIMsgDatabase** originalDB) { nsCOMPtr returnOp; nsOfflineImapOperationType opType; op->GetOperation(&opType); NS_ASSERTION(opType & nsIMsgOfflineImapOperation::kMoveResult, "not an offline move op"); nsCString sourceFolderURI; op->GetSourceFolderURI(sourceFolderURI); nsresult rv; nsCOMPtr sourceFolder; rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr folderInfo; sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB); if (*originalDB) { nsCOMPtr opsDb = do_QueryInterface(*originalDB, &rv); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey originalKey; op->GetMessageKey(&originalKey); rv = opsDb->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp)); if (NS_SUCCEEDED(rv) && returnOp) { nsCString moveDestination; nsCString thisFolderURI; GetURI(thisFolderURI); returnOp->GetDestinationFolderURI(moveDestination); if (moveDestination.Equals(thisFolderURI)) returnOp->ClearOperation(nsIMsgOfflineImapOperation::kMoveResult); } } returnOp.forget(originalOp); return rv; } nsresult nsImapMailFolder::GetOriginalOp( nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp, nsIMsgDatabase** originalDB) { nsCOMPtr returnOp; nsCString sourceFolderURI; op->GetSourceFolderURI(sourceFolderURI); nsresult rv; nsCOMPtr sourceFolder; rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr folderInfo; sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB); if (*originalDB) { nsCOMPtr opsDb = do_QueryInterface(*originalDB, &rv); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey originalKey; op->GetMessageKey(&originalKey); rv = opsDb->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp)); } returnOp.forget(originalOp); return rv; } nsresult nsImapMailFolder::FindOpenRange(nsMsgKey& fakeBase, uint32_t srcCount) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey newBase = fakeBase - 1; uint32_t freeCount = 0; while (freeCount != srcCount && newBase > 0) { bool containsKey; if (NS_SUCCEEDED(mDatabase->ContainsKey(newBase, &containsKey)) && !containsKey) freeCount++; else freeCount = 0; newBase--; } if (!newBase) return NS_ERROR_FAILURE; fakeBase = newBase; return NS_OK; } // Helper to synchronously copy a message from one msgStore to another. static nsresult CopyStoreMessage(nsIMsgDBHdr* srcHdr, nsIMsgDBHdr* destHdr, uint64_t& bytesCopied) { nsresult rv; // Boilerplate setup. nsCOMPtr srcFolder; rv = srcHdr->GetFolder(getter_AddRefs(srcFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr destFolder; rv = destHdr->GetFolder(getter_AddRefs(destFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr destStore; rv = destFolder->GetMsgStore(getter_AddRefs(destStore)); NS_ENSURE_SUCCESS(rv, rv); // Copy message into the msgStore. nsCOMPtr srcStream; rv = srcFolder->GetLocalMsgStream(srcHdr, getter_AddRefs(srcStream)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr destStream; rv = destFolder->GetOfflineStoreOutputStream(destHdr, getter_AddRefs(destStream)); NS_ENSURE_SUCCESS(rv, rv); rv = SyncCopyStream(srcStream, destStream, bytesCopied); if (NS_SUCCEEDED(rv)) { rv = destStore->FinishNewMessage(destStream, destHdr); } else { destStore->DiscardNewMessage(destStream, destHdr); } return rv; } // This imap folder is the destination of an offline move/copy. // We are either offline, or doing a pseudo-offline delete (where we do an // offline delete, load the next message, then playback the offline delete). nsresult nsImapMailFolder::CopyMessagesOffline( nsIMsgFolder* srcFolder, nsTArray> const& messages, bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) { nsresult rv; nsresult stopit = NS_OK; nsCOMPtr sourceMailDB; nsCOMPtr srcDbFolderInfo; srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo), getter_AddRefs(sourceMailDB)); bool deleteToTrash = false; bool deleteImmediately = false; uint32_t srcCount = messages.Length(); nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); nsTArray> msgHdrsCopied; nsTArray> destMsgHdrs; if (NS_SUCCEEDED(rv) && imapServer) { nsMsgImapDeleteModel deleteModel; imapServer->GetDeleteModel(&deleteModel); deleteToTrash = (deleteModel == nsMsgImapDeleteModels::MoveToTrash); deleteImmediately = (deleteModel == nsMsgImapDeleteModels::DeleteNoTrash); } // This array is used only when we are actually removing the messages from the // source database. nsTArray keysToDelete( (isMove && (deleteToTrash || deleteImmediately)) ? srcCount : 0); if (sourceMailDB) { // save the future ops in the source DB, if this is not a imap->local // copy/move nsCOMPtr txnMgr; if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) txnMgr->BeginBatch(nullptr); nsCOMPtr database; GetMsgDatabase(getter_AddRefs(database)); if (database) { // get the highest key in the dest db, so we can make up our fake keys nsMsgKey fakeBase = 1; nsCOMPtr folderInfo; rv = database->GetDBFolderInfo(getter_AddRefs(folderInfo)); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey highWaterMark = nsMsgKey_None; folderInfo->GetHighWater(&highWaterMark); fakeBase += highWaterMark; nsMsgKey fakeTop = fakeBase + srcCount; // Check that we have enough room for the fake headers. If fakeTop // is <= highWaterMark, we've overflowed. if (fakeTop <= highWaterMark || fakeTop == nsMsgKey_None) { rv = FindOpenRange(fakeBase, srcCount); NS_ENSURE_SUCCESS(rv, rv); } // N.B. We must not return out of the for loop - we need the matching // end notifications to be sent. // We don't need to acquire the semaphor since this is synchronous // on the UI thread but we should check if the offline store is locked. bool isLocked; GetLocked(&isLocked); nsTArray addedKeys; nsTArray srcKeyArray; nsCOMArray addedHdrs; nsCOMArray srcMsgs; nsOfflineImapOperationType moveCopyOpType; nsOfflineImapOperationType deleteOpType = nsIMsgOfflineImapOperation::kDeletedMsg; if (!deleteToTrash) deleteOpType = nsIMsgOfflineImapOperation::kMsgMarkedDeleted; nsCString messageIds; rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray); // put fake message in destination db, delete source if move EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false); nsCString originalSrcFolderURI; srcFolder->GetURI(originalSrcFolderURI); nsCOMPtr opsDb = do_QueryInterface(sourceMailDB, &rv); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t sourceKeyIndex = 0; NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount); sourceKeyIndex++) { bool messageReturningHome = false; RefPtr message = messages[sourceKeyIndex]; nsMsgKey originalKey; if (message) { rv = message->GetMessageKey(&originalKey); } else { NS_ERROR("bad msg in src array"); continue; } nsCOMPtr sourceOp; rv = opsDb->GetOfflineOpForKey(originalKey, true, getter_AddRefs(sourceOp)); if (NS_SUCCEEDED(rv) && sourceOp) { srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents); nsCOMPtr originalDB; nsOfflineImapOperationType opType; sourceOp->GetOperation(&opType); // if we already have an offline op for this key, then we need to see // if it was moved into the source folder while offline if (opType == nsIMsgOfflineImapOperation::kMoveResult) // offline move { // gracious me, we are moving something we already moved while // offline! find the original operation and clear it! nsCOMPtr originalOp; GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp), getter_AddRefs(originalDB)); nsCOMPtr opsDbOriginal = do_QueryInterface(originalDB, &rv); if (NS_SUCCEEDED(rv) && originalOp) { nsCString srcFolderURI; srcFolder->GetURI(srcFolderURI); sourceOp->GetSourceFolderURI(originalSrcFolderURI); sourceOp->GetMessageKey(&originalKey); if (isMove) opsDb->RemoveOfflineOp(sourceOp); sourceOp = originalOp; if (originalSrcFolderURI.Equals(srcFolderURI)) { messageReturningHome = true; opsDbOriginal->RemoveOfflineOp(originalOp); } } } if (!messageReturningHome) { nsCString folderURI; GetURI(folderURI); if (isMove) { uint32_t msgSize; uint32_t msgFlags; imapMessageFlagsType newImapFlags = 0; message->GetMessageSize(&msgSize); message->GetFlags(&msgFlags); sourceOp->SetDestinationFolderURI(folderURI); // offline move sourceOp->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved); sourceOp->SetMsgSize(msgSize); newImapFlags = msgFlags & 0x7; if (msgFlags & nsMsgMessageFlags::Forwarded) newImapFlags |= kImapMsgForwardedFlag; sourceOp->SetNewFlags(newImapFlags); } else { sourceOp->AddMessageCopyOperation(folderURI); // offline copy } sourceOp->GetOperation(&moveCopyOpType); srcMsgs.AppendObject(message); } } else { stopit = NS_ERROR_FAILURE; } nsCOMPtr mailHdr; rv = sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr)); if (NS_SUCCEEDED(rv) && mailHdr) { // Copy the DB hdr into the destination folder. bool successfulCopy = false; nsMsgKey srcDBhighWaterMark; srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark); nsCOMPtr newMailHdr; rv = database->CopyHdrFromExistingHdr(fakeBase + sourceKeyIndex, mailHdr, true, getter_AddRefs(newMailHdr)); if (!newMailHdr || NS_FAILED(rv)) { NS_ASSERTION(false, "failed to copy hdr"); stopit = rv; } if (NS_SUCCEEDED(stopit)) { bool hasMsgOffline = false; destMsgHdrs.AppendElement(newMailHdr); srcFolder->HasMsgOffline(originalKey, &hasMsgOffline); newMailHdr->SetUint32Property("pseudoHdr", 1); if (hasMsgOffline && !isLocked) { uint64_t bytesCopied; stopit = CopyStoreMessage(mailHdr, newMailHdr, bytesCopied); if (NS_SUCCEEDED(stopit)) { uint32_t unused; newMailHdr->OrFlags(nsMsgMessageFlags::Offline, &unused); newMailHdr->SetOfflineMessageSize(bytesCopied); } } else { database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr); } nsCOMPtr opsDb = do_QueryInterface(database, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr destOp; opsDb->GetOfflineOpForKey(fakeBase + sourceKeyIndex, true, getter_AddRefs(destOp)); if (destOp) { // check if this is a move back to the original mailbox, in which // case we just delete the offline operation. if (messageReturningHome) { opsDb->RemoveOfflineOp(destOp); } else { SetFlag(nsMsgFolderFlags::OfflineEvents); destOp->SetSourceFolderURI(originalSrcFolderURI); destOp->SetSrcMessageKey(originalKey); addedKeys.AppendElement(fakeBase + sourceKeyIndex); addedHdrs.AppendObject(newMailHdr); } } else { stopit = NS_ERROR_FAILURE; } } successfulCopy = NS_SUCCEEDED(stopit); nsMsgKey msgKey; mailHdr->GetMessageKey(&msgKey); if (isMove && successfulCopy) { if (deleteToTrash || deleteImmediately) keysToDelete.AppendElement(msgKey); else sourceMailDB->MarkImapDeleted(msgKey, true, nullptr); // offline delete } if (successfulCopy) { // This is for both moves and copies msgHdrsCopied.AppendElement(mailHdr); } } } // End message loop. EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true); RefPtr addHdrMsgTxn = new nsImapOfflineTxn( this, &addedKeys, nullptr, this, isMove, nsIMsgOfflineImapOperation::kAddedHeader, addedHdrs); if (addHdrMsgTxn && txnMgr) txnMgr->DoTransaction(addHdrMsgTxn); RefPtr undoMsgTxn = new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this, isMove, moveCopyOpType, srcMsgs); if (undoMsgTxn) { if (isMove) { undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg); nsCOMPtr srcIsImap( do_QueryInterface(srcFolder)); // remember this undo transaction so we can hook up the result // msg ids in the undo transaction. if (srcIsImap) { nsImapMailFolder* srcImapFolder = static_cast(srcFolder); srcImapFolder->m_pendingOfflineMoves.AppendElement(undoMsgTxn); } } else { undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg); } // we're adding this undo action before the delete is successful. This // is evil, but 4.5 did it as well. if (txnMgr) txnMgr->DoTransaction(undoMsgTxn); } undoMsgTxn = new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this, isMove, deleteOpType, srcMsgs); if (undoMsgTxn) { if (isMove) { if (mFlags & nsMsgFolderFlags::Trash) { undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); } else { undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg); } } else { undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg); } if (txnMgr) txnMgr->DoTransaction(undoMsgTxn); } if (isMove) sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit); database->Commit(nsMsgDBCommitType::kLargeCommit); SummaryChanged(); srcFolder->SummaryChanged(); } if (txnMgr) txnMgr->EndBatch(false); } if (!msgHdrsCopied.IsEmpty()) { nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) { notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this, destMsgHdrs); } } // NOTE (Bug 1787963): // If we're performing a move, by rights we should be deleting the source // message(s) here. But that would mean they won't be available when we try // to run the offline move operation once we're back online. So we'll just // leave things as they are: // - the message(s) copied into the destination folder // - the original message(s) left in the source folder // - the offline move operation all queued up for when we go back online // When we do go back online, the offline move op will be performed and // the source message(s) will be deleted. For real. // Would be nice to have some marker to hide or grey out messages which are // in this state of impending doom... but it's a pretty obscure corner case // and we've already got quite enough of those. // // BUT... CopyMessagesOffline() is also used when online (ha!), *if* we're // copying between folders on the same nsIMsgIncomingServer, in order to // support undo. In that case we _do_ want to go ahead with the delete now. bool sameServer; rv = IsOnSameServer(srcFolder, this, &sameServer); if (NS_SUCCEEDED(rv) && sameServer && isMove && (deleteToTrash || deleteImmediately)) { DeleteStoreMessages(keysToDelete, srcFolder); srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false); sourceMailDB->DeleteMessages(keysToDelete, nullptr); srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true); } nsCOMPtr srcSupport = do_QueryInterface(srcFolder); OnCopyCompleted(srcSupport, rv); if (isMove) { srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted : kDeleteOrMoveMsgFailed); } return rv; } void nsImapMailFolder::SetPendingAttributes( const nsTArray>& messages, bool aIsMove, bool aSetOffline) { GetDatabase(); if (!mDatabase) return; uint32_t supportedUserFlags; GetSupportedUserFlags(&supportedUserFlags); nsresult rv; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS_VOID(rv); nsCString dontPreserve; // These preferences exist so that extensions can control which properties // are preserved in the database when a message is moved or copied. All // properties are preserved except those listed in these preferences if (aIsMove) prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove", dontPreserve); else prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy", dontPreserve); // We'll add spaces at beginning and end so we can search for space-name-space nsCString dontPreserveEx(" "_ns); dontPreserveEx.Append(dontPreserve); dontPreserveEx.Append(' '); // these properties are set as integers below, so don't set them again // in the iteration through the properties dontPreserveEx.AppendLiteral( "offlineMsgSize msgOffset flags priority pseudoHdr "); // these fields are either copied separately when the server does not support // custom IMAP flags, or managed directly through the flags dontPreserveEx.AppendLiteral("keywords label "); // check if any msg hdr has special flags or properties set // that we need to set on the dest hdr for (auto msgDBHdr : messages) { if (!(supportedUserFlags & kImapMsgSupportUserFlag)) { nsCString keywords; msgDBHdr->GetStringProperty("keywords", keywords); if (!keywords.IsEmpty()) mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "keywords", keywords.get()); } nsTArray properties; nsresult rv = msgDBHdr->GetProperties(properties); NS_ENSURE_SUCCESS_VOID(rv); nsCString sourceString; for (auto property : properties) { nsAutoCString propertyEx(" "_ns); propertyEx.Append(property); propertyEx.Append(' '); if (dontPreserveEx.Find(propertyEx) != kNotFound) continue; nsCString sourceString; msgDBHdr->GetStringProperty(property.get(), sourceString); mDatabase->SetAttributeOnPendingHdr(msgDBHdr, property.get(), sourceString.get()); } // Carry over HasRe flag. uint32_t flags; uint32_t storeFlags = 0; msgDBHdr->GetFlags(&flags); if (flags & nsMsgMessageFlags::HasRe) { storeFlags = nsMsgMessageFlags::HasRe; mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "flags", storeFlags); } uint32_t messageSize; uint64_t messageOffset; nsCString storeToken; msgDBHdr->GetMessageOffset(&messageOffset); msgDBHdr->GetOfflineMessageSize(&messageSize); msgDBHdr->GetStringProperty("storeToken", storeToken); if (messageSize) { mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "offlineMsgSize", messageSize); mDatabase->SetUint64AttributeOnPendingHdr(msgDBHdr, "msgOffset", messageOffset); // Not always setting "flags" attribute to nsMsgMessageFlags::Offline // here because it can cause missing parts (inline or attachments) // when messages are moved or copied manually or by filter action. if (aSetOffline) mDatabase->SetUint32AttributeOnPendingHdr( msgDBHdr, "flags", storeFlags | nsMsgMessageFlags::Offline); mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "storeToken", storeToken.get()); } nsMsgPriorityValue priority; msgDBHdr->GetPriority(&priority); if (priority != 0) { nsAutoCString priorityStr; priorityStr.AppendInt(priority); mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "priority", priorityStr.get()); } } } NS_IMETHODIMP nsImapMailFolder::CopyMessages( nsIMsgFolder* srcFolder, nsTArray> const& messages, bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener, bool isFolder, // isFolder for future use when we do cross-server folder // move/copy bool allowUndo) { UpdateTimestamps(allowUndo); nsresult rv; nsCOMPtr srcSupport = do_QueryInterface(srcFolder); bool sameServer; rv = IsOnSameServer(srcFolder, this, &sameServer); if (NS_FAILED(rv)) goto done; // in theory, if allowUndo is true, then this is a user initiated // action, and we should do it pseudo-offline. If it's not // user initiated (e.g., mail filters firing), then allowUndo is // false, and we should just do the action. if (!WeAreOffline() && sameServer && allowUndo) { // complete the copy operation as in offline mode rv = CopyMessagesOffline(srcFolder, messages, isMove, msgWindow, listener); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error offline copy"); // We'll warn if this fails, but we should still try to play back // offline ops, because it's possible the copy got far enough to // create the offline ops. // We make sure that the source folder is an imap folder by limiting // pseudo-offline operations to the same imap server. If we extend the code // to cover non imap folders in the future (i.e. imap folder->local folder), // then the following downcast will cause either a crash or compiler error. // Do not forget to change it accordingly. nsImapMailFolder* srcImapFolder = static_cast(srcFolder); // if there is no pending request, create a new one, and set the timer. // Otherwise use the existing one to reset the timer. it is callback // function's responsibility to delete the new request object if (!srcImapFolder->m_pendingPlaybackReq) { srcImapFolder->m_pendingPlaybackReq = new nsPlaybackRequest(srcImapFolder, msgWindow); } // Create and start a new playback one-shot timer. If there is already a // timer created that has not timed out, cancel it. if (srcImapFolder->m_playbackTimer) srcImapFolder->m_playbackTimer->Cancel(); rv = NS_NewTimerWithFuncCallback( getter_AddRefs(srcImapFolder->m_playbackTimer), PlaybackTimerCallback, (void*)srcImapFolder->m_pendingPlaybackReq, PLAYBACK_TIMER_INTERVAL_IN_MS, nsITimer::TYPE_ONE_SHOT, "nsImapMailFolder::PlaybackTimerCallback", nullptr); if (NS_FAILED(rv)) { NS_WARNING("Could not start m_playbackTimer timer"); } return rv; } else { // sort the message array by key nsTArray keyArray(messages.Length()); for (nsIMsgDBHdr* aMessage : messages) { if (!aMessage) { continue; } nsMsgKey key; aMessage->GetMessageKey(&key); keyArray.AppendElement(key); } keyArray.Sort(); nsTArray> sortedMsgs; rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs); NS_ENSURE_SUCCESS(rv, rv); if (WeAreOffline()) return CopyMessagesOffline(srcFolder, sortedMsgs, isMove, msgWindow, listener); nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // 3rd parameter: Do not set offline flag. SetPendingAttributes(sortedMsgs, isMove, false); // if the folders aren't on the same server, do a stream base copy if (!sameServer) { rv = CopyMessagesWithStream(srcFolder, sortedMsgs, isMove, true, msgWindow, listener, allowUndo); goto done; } nsAutoCString messageIds; rv = AllocateUidStringFromKeys(keyArray, messageIds); if (NS_FAILED(rv)) goto done; nsCOMPtr urlListener; rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener)); rv = InitCopyState(srcSupport, sortedMsgs, isMove, true, false, 0, EmptyCString(), listener, msgWindow, allowUndo); if (NS_FAILED(rv)) goto done; m_copyState->m_curIndex = m_copyState->m_messages.Length(); if (isMove) srcFolder->EnableNotifications( allMessageCountNotifications, false); // disable message count notification nsCOMPtr resultUrl; nsCOMPtr copySupport = do_QueryInterface(m_copyState); rv = imapService->OnlineMessageCopy( srcFolder, messageIds, this, true, isMove, urlListener, getter_AddRefs(resultUrl), copySupport, msgWindow); if (NS_SUCCEEDED(rv) && m_copyState->m_allowUndo) { RefPtr undoMsgTxn = new nsImapMoveCopyMsgTxn; if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(srcFolder, &keyArray, messageIds.get(), this, true, isMove))) return NS_ERROR_OUT_OF_MEMORY; if (isMove) { if (mFlags & nsMsgFolderFlags::Trash) undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); else undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg); } else undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg); m_copyState->m_undoMsgTxn = undoMsgTxn; } } // endif done: if (NS_FAILED(rv)) { (void)OnCopyCompleted(srcSupport, rv); if (isMove) { srcFolder->EnableNotifications( allMessageCountNotifications, true); // enable message count notification NotifyFolderEvent(kDeleteOrMoveMsgFailed); } } return rv; } // This is used when copying an imap or local/pop3 folder to an imap server. // It does not allow completely moving an imap or local/pop3 folder to an imap // server since only the messages can be moved between servers. class nsImapFolderCopyState final : public nsIUrlListener, public nsIMsgCopyServiceListener { public: nsImapFolderCopyState(nsIMsgFolder* destParent, nsIMsgFolder* srcFolder, bool isMoveMessages, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener); NS_DECL_ISUPPORTS NS_DECL_NSIURLLISTENER NS_DECL_NSIMSGCOPYSERVICELISTENER nsresult StartNextCopy(); nsresult AdvanceToNextFolder(nsresult aStatus); protected: ~nsImapFolderCopyState(); RefPtr m_newDestFolder; nsCOMPtr m_origSrcFolder; nsCOMPtr m_curDestParent; nsCOMPtr m_curSrcFolder; bool m_isMoveMessages; nsCOMPtr m_copySrvcListener; nsCOMPtr m_msgWindow; int32_t m_childIndex; nsCOMArray m_srcChildFolders; nsCOMArray m_destParents; }; NS_IMPL_ISUPPORTS(nsImapFolderCopyState, nsIUrlListener, nsIMsgCopyServiceListener) nsImapFolderCopyState::nsImapFolderCopyState( nsIMsgFolder* destParent, nsIMsgFolder* srcFolder, bool isMoveMessages, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) { m_origSrcFolder = do_QueryInterface(srcFolder); m_curDestParent = destParent; m_curSrcFolder = srcFolder; m_isMoveMessages = isMoveMessages; m_msgWindow = msgWindow; m_copySrvcListener = listener; m_childIndex = -1; // NOTE: The nsImapMailFolder doesn't keep a reference to us, so we're // relying on our use as a listener by nsImapService and nsMsgCopyService // to keep our refcount from zeroing! // Might be safer to add a kungfudeathgrip on ourselves for the duration // of the operation? Would need to make sure we catch all error conditions. } nsImapFolderCopyState::~nsImapFolderCopyState() {} nsresult nsImapFolderCopyState::StartNextCopy() { nsresult rv; // Create the destination folder (our OnStopRunningUrl() will be called // when done). nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsString folderName; m_curSrcFolder->GetName(folderName); return imapService->EnsureFolderExists(m_curDestParent, folderName, m_msgWindow, this); } nsresult nsImapFolderCopyState::AdvanceToNextFolder(nsresult aStatus) { nsresult rv = NS_OK; m_childIndex++; if (m_childIndex >= m_srcChildFolders.Count()) { if (m_newDestFolder) m_newDestFolder->OnCopyCompleted(m_origSrcFolder, aStatus); } else { m_curDestParent = m_destParents[m_childIndex]; m_curSrcFolder = m_srcChildFolders[m_childIndex]; rv = StartNextCopy(); } return rv; } NS_IMETHODIMP nsImapFolderCopyState::OnStartRunningUrl(nsIURI* aUrl) { NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url"); return NS_OK; } NS_IMETHODIMP nsImapFolderCopyState::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) { if (NS_FAILED(aExitCode)) { if (m_copySrvcListener) m_copySrvcListener->OnStopCopy(aExitCode); return aExitCode; // or NS_OK??? } nsresult rv = NS_OK; if (aUrl) { nsCOMPtr imapUrl = do_QueryInterface(aUrl); if (imapUrl) { nsImapAction imapAction = nsIImapUrl::nsImapTest; imapUrl->GetImapAction(&imapAction); switch (imapAction) { case nsIImapUrl::nsImapEnsureExistsFolder: { // Our EnsureFolderExists() call has completed successfully, // so our dest folder is ready. nsCOMPtr newMsgFolder; nsString folderName; nsCString utfLeafName; m_curSrcFolder->GetName(folderName); bool utf8AcceptEnabled; nsCOMPtr imapFolder = do_QueryInterface(m_curDestParent); rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled); NS_ENSURE_SUCCESS(rv, rv); if (utf8AcceptEnabled) { CopyUTF16toUTF8(folderName, utfLeafName); } else { CopyUTF16toMUTF7(folderName, utfLeafName); } // Create the nsIMsgFolder object which represents the folder on // the IMAP server. rv = m_curDestParent->FindSubFolder(utfLeafName, getter_AddRefs(newMsgFolder)); NS_ENSURE_SUCCESS(rv, rv); // Save the first new folder so we can send a notification to the // copy service when this whole process is done. if (!m_newDestFolder) m_newDestFolder = static_cast(newMsgFolder.get()); // Check if the source folder has children. If it does, list them // into m_srcChildFolders, and set m_destParents for the // corresponding indexes to the newly created folder. nsTArray> subFolders; rv = m_curSrcFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); uint32_t childIndex = 0; for (nsIMsgFolder* folder : subFolders) { m_srcChildFolders.InsertElementAt(m_childIndex + childIndex + 1, folder); m_destParents.InsertElementAt(m_childIndex + childIndex + 1, newMsgFolder); ++childIndex; } // Now kick off a copy (or move) of messages to the new folder. nsCOMPtr enumerator; rv = m_curSrcFolder->GetMessages(getter_AddRefs(enumerator)); nsTArray> msgArray; bool hasMore = false; if (enumerator) rv = enumerator->HasMoreElements(&hasMore); // Early-out for empty folder. if (!hasMore) return AdvanceToNextFolder(NS_OK); while (NS_SUCCEEDED(rv) && hasMore) { nsCOMPtr hdr; rv = enumerator->GetNext(getter_AddRefs(hdr)); NS_ENSURE_SUCCESS(rv, rv); msgArray.AppendElement(hdr); rv = enumerator->HasMoreElements(&hasMore); } nsCOMPtr copyService = do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = copyService->CopyMessages(m_curSrcFolder, msgArray, newMsgFolder, m_isMoveMessages, this, m_msgWindow, false /* allowUndo */); } break; } } } return rv; } NS_IMETHODIMP nsImapFolderCopyState::OnStartCopy() { return NS_OK; } /* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */ NS_IMETHODIMP nsImapFolderCopyState::OnProgress(uint32_t aProgress, uint32_t aProgressMax) { return NS_ERROR_NOT_IMPLEMENTED; } /* void SetMessageKey (in nsMsgKey aKey); */ NS_IMETHODIMP nsImapFolderCopyState::SetMessageKey(nsMsgKey aKey) { return NS_ERROR_NOT_IMPLEMENTED; } /* [noscript] void GetMessageId (in nsCString aMessageId); */ NS_IMETHODIMP nsImapFolderCopyState::GetMessageId(nsACString& messageId) { return NS_ERROR_NOT_IMPLEMENTED; } /* void OnStopCopy (in nsresult aStatus); */ NS_IMETHODIMP nsImapFolderCopyState::OnStopCopy(nsresult aStatus) { if (NS_SUCCEEDED(aStatus)) return AdvanceToNextFolder(aStatus); if (m_copySrvcListener) { (void)m_copySrvcListener->OnStopCopy(aStatus); m_copySrvcListener = nullptr; } return NS_OK; } // "this" is the destination (parent) imap folder that srcFolder is copied to. // srcFolder may be another imap or a local/pop3 folder. NS_IMETHODIMP nsImapMailFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) { NS_ENSURE_ARG_POINTER(srcFolder); nsresult rv; bool sameServer; rv = IsOnSameServer(this, srcFolder, &sameServer); NS_ENSURE_SUCCESS(rv, rv); if (sameServer && isMoveFolder) { // Do a pure folder move within the same IMAP account/server, where // "pure" means the folder AND messages are copied to the destination and // then both are removed from source account. uint32_t folderFlags = 0; if (srcFolder) srcFolder->GetFlags(&folderFlags); // if our source folder is a virtual folder if (folderFlags & nsMsgFolderFlags::Virtual) { nsCOMPtr newMsgFolder; nsString folderName; srcFolder->GetName(folderName); nsAutoString safeFolderName(folderName); NS_MsgHashIfNecessary(safeFolderName); srcFolder->ForceDBClosed(); nsCOMPtr oldPathFile; rv = srcFolder->GetFilePath(getter_AddRefs(oldPathFile)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr summaryFile; GetSummaryFileLocation(oldPathFile, getter_AddRefs(summaryFile)); nsCOMPtr newPathFile; rv = GetFilePath(getter_AddRefs(newPathFile)); NS_ENSURE_SUCCESS(rv, rv); bool isDirectory = false; newPathFile->IsDirectory(&isDirectory); if (!isDirectory) { AddDirectorySeparator(newPathFile); rv = newPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700); NS_ENSURE_SUCCESS(rv, rv); } rv = CheckIfFolderExists(folderName, this, msgWindow); if (NS_FAILED(rv)) return rv; rv = summaryFile->CopyTo(newPathFile, EmptyString()); NS_ENSURE_SUCCESS(rv, rv); rv = AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder)); NS_ENSURE_SUCCESS(rv, rv); newMsgFolder->SetPrettyName(folderName); uint32_t flags; srcFolder->GetFlags(&flags); newMsgFolder->SetFlags(flags); NotifyFolderAdded(newMsgFolder); // now remove the old folder nsCOMPtr msgParent; srcFolder->GetParent(getter_AddRefs(msgParent)); srcFolder->SetParent(nullptr); if (msgParent) { // The files have already been moved, so delete storage false. msgParent->PropagateDelete(srcFolder, false); oldPathFile->Remove(false); // berkeley mailbox srcFolder->DeleteStorage(); nsCOMPtr parentPathFile; rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile)); NS_ENSURE_SUCCESS(rv, rv); AddDirectorySeparator(parentPathFile); nsCOMPtr children; parentPathFile->GetDirectoryEntries(getter_AddRefs(children)); bool more; // checks if the directory is empty or not if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more) parentPathFile->Remove(true); } } else // non-virtual folder { nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr srcSupport = do_QueryInterface(srcFolder); bool match = false; bool confirmed = false; if (mFlags & nsMsgFolderFlags::Trash) { rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match); if (match) { srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed); // should we return an error to copy service? // or send a notification? if (!confirmed) return NS_OK; } } rv = InitCopyState(srcSupport, {}, false, false, false, 0, EmptyCString(), listener, msgWindow, false); if (NS_FAILED(rv)) return OnCopyCompleted(srcSupport, rv); rv = imapService->MoveFolder(srcFolder, this, this, msgWindow); } } else { // !sameServer OR it's a copy. Unit tests expect a successful folder // copy within the same IMAP server even though the UI forbids copy and // only allows moves inside the same server. folderCopier, set below, // handles the folder copy within an IMAP server (needed by unit tests) and // the folder move or copy from another account or server into an IMAP // account/server. The folder move from another account is "impure" since // just the messages are moved and the source folder remains in place. RefPtr folderCopier = new nsImapFolderCopyState( this, srcFolder, isMoveFolder, // Always copy folders; if true only move the messages msgWindow, listener); // NOTE: the copystate object must hold itself in existence until complete, // as we're not keeping hold of it here. rv = folderCopier->StartNextCopy(); } return rv; } NS_IMETHODIMP nsImapMailFolder::CopyFileMessage(nsIFile* file, nsIMsgDBHdr* msgToReplace, bool isDraftOrTemplate, uint32_t aNewMsgFlags, const nsACString& aNewMsgKeywords, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) { nsresult rv = NS_ERROR_NULL_POINTER; nsMsgKey key = nsMsgKey_None; nsAutoCString messageId; nsTArray> messages; nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); if (NS_FAILED(rv)) return OnCopyCompleted(file, rv); if (msgToReplace) { rv = msgToReplace->GetMessageKey(&key); if (NS_SUCCEEDED(rv)) { messageId.AppendInt((int32_t)key); // We have an existing message to replace because the user has deleted or // detached one or more attachments. So tell SetPendingAttributes() to // not set several pending offline items (offset, message size, etc.) for // the message to be replaced by setting message size temporarily to zero. // The original message is not actually "replaced" but is imap deleted // and a new message with the same body but with some deleted or detached // attachments is imap appended from the file to the folder. uint32_t saveMsgSize; msgToReplace->GetOfflineMessageSize(&saveMsgSize); msgToReplace->SetOfflineMessageSize(0); SetPendingAttributes({msgToReplace}, false, false); msgToReplace->SetOfflineMessageSize(saveMsgSize); messages.AppendElement(msgToReplace); } } bool isMove = (msgToReplace ? true : false); rv = InitCopyState(file, messages, isMove, isDraftOrTemplate, false, aNewMsgFlags, aNewMsgKeywords, listener, msgWindow, false); if (NS_FAILED(rv)) return OnCopyCompleted(file, rv); m_copyState->m_streamCopy = true; rv = imapService->AppendMessageFromFile(file, this, messageId, true, isDraftOrTemplate, this, m_copyState, msgWindow); if (NS_FAILED(rv)) return OnCopyCompleted(file, rv); return rv; } nsresult nsImapMailFolder::CopyStreamMessage( nsIMsgDBHdr* message, nsIMsgFolder* dstFolder, // should be this nsIMsgWindow* aMsgWindow, bool isMove) { NS_ENSURE_ARG_POINTER(message); if (!m_copyState) MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreamMessage failed with null m_copyState")); NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER); nsresult rv; nsCOMPtr copyStreamListener = do_CreateInstance( "@mozilla.org/messenger/copymessagestreamlistener;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr copyListener( do_QueryInterface(dstFolder, &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr srcFolder( do_QueryInterface(m_copyState->m_srcSupport, &rv)); if (NS_FAILED(rv)) MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreaMessage failed with null m_copyState->m_srcSupport")); if (NS_FAILED(rv)) return rv; rv = copyStreamListener->Init(copyListener); if (NS_FAILED(rv)) MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreaMessage failed in copyStreamListener->Init")); if (NS_FAILED(rv)) return rv; nsCString uri; srcFolder->GetUriForMsg(message, uri); if (!m_copyState->m_msgService) rv = GetMessageServiceFromURI(uri, getter_AddRefs(m_copyState->m_msgService)); if (NS_SUCCEEDED(rv) && m_copyState->m_msgService) { nsCOMPtr streamListener( do_QueryInterface(copyStreamListener, &rv)); NS_ENSURE_SUCCESS(rv, rv); // put up status message here, if copying more than one message. if (m_copyState->m_messages.Length() > 1) { nsString dstFolderName, progressText; GetName(dstFolderName); nsAutoString curMsgString; nsAutoString totalMsgString; totalMsgString.AppendInt((int32_t)m_copyState->m_messages.Length()); curMsgString.AppendInt(m_copyState->m_curIndex + 1); AutoTArray formatStrings = {curMsgString, totalMsgString, dstFolderName}; nsCOMPtr bundle; rv = IMAPGetStringBundle(getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); rv = bundle->FormatStringFromName("imapCopyingMessageOf2", formatStrings, progressText); nsCOMPtr statusFeedback; if (m_copyState->m_msgWindow) m_copyState->m_msgWindow->GetStatusFeedback( getter_AddRefs(statusFeedback)); if (statusFeedback) { statusFeedback->ShowStatusString(progressText); int32_t percent; percent = (100 * m_copyState->m_curIndex) / (int32_t)m_copyState->m_messages.Length(); statusFeedback->ShowProgress(percent); } } rv = m_copyState->m_msgService->CopyMessage( uri, streamListener, isMove && !m_copyState->m_isCrossServerOp, nullptr, aMsgWindow); if (NS_FAILED(rv)) MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyMessage failed: uri %s", uri.get())); } return rv; } nsImapMailCopyState::nsImapMailCopyState() : m_isMove(false), m_selectedState(false), m_isCrossServerOp(false), m_curIndex(0), m_streamCopy(false), m_dataBuffer(nullptr), m_dataBufferSize(0), m_leftOver(0), m_allowUndo(false), m_eatLF(false), m_newMsgFlags(0), m_appendUID(nsMsgKey_None) {} nsImapMailCopyState::~nsImapMailCopyState() { PR_Free(m_dataBuffer); if (m_tmpFile) m_tmpFile->Remove(false); } NS_IMPL_ISUPPORTS(nsImapMailCopyState, nsImapMailCopyState) nsresult nsImapMailFolder::InitCopyState( nsISupports* srcSupport, nsTArray> const& messages, bool isMove, bool selectedState, bool acrossServers, uint32_t newMsgFlags, const nsACString& newMsgKeywords, nsIMsgCopyServiceListener* listener, nsIMsgWindow* msgWindow, bool allowUndo) { NS_ENSURE_ARG_POINTER(srcSupport); NS_ENSURE_TRUE(!m_copyState, NS_ERROR_FAILURE); m_copyState = new nsImapMailCopyState(); m_copyState->m_isCrossServerOp = acrossServers; m_copyState->m_srcSupport = srcSupport; m_copyState->m_messages = messages.Clone(); if (!m_copyState->m_isCrossServerOp) { uint32_t numUnread = 0; for (nsIMsgDBHdr* message : m_copyState->m_messages) { // if the message is not there, then assume what the caller tells us to. bool isRead = false; uint32_t flags; if (message) { message->GetFlags(&flags); isRead = flags & nsMsgMessageFlags::Read; } if (!isRead) numUnread++; } m_copyState->m_unreadCount = numUnread; } else { nsIMsgDBHdr* message = m_copyState->m_messages[m_copyState->m_curIndex]; // if the key is not there, then assume what the caller tells us to. bool isRead = false; uint32_t flags; if (message) { message->GetFlags(&flags); isRead = flags & nsMsgMessageFlags::Read; } m_copyState->m_unreadCount = (isRead) ? 0 : 1; } m_copyState->m_isMove = isMove; m_copyState->m_newMsgFlags = newMsgFlags; m_copyState->m_newMsgKeywords = newMsgKeywords; m_copyState->m_allowUndo = allowUndo; m_copyState->m_selectedState = selectedState; m_copyState->m_msgWindow = msgWindow; if (listener) m_copyState->m_listener = listener; return NS_OK; } nsresult nsImapMailFolder::CopyFileToOfflineStore(nsIFile* srcFile, nsMsgKey msgKey) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); bool storeOffline = (mFlags & nsMsgFolderFlags::Offline) && !WeAreOffline(); if (msgKey == nsMsgKey_None) { // To support send filters, we need to store the message in the database // when it is copied to the FCC folder. In that case, we know the UID of the // message and therefore have the correct msgKey. In other cases, where // we don't need the offline message copied, don't add to db. if (!storeOffline) return NS_OK; mDatabase->GetNextFakeOfflineMsgKey(&msgKey); } nsCOMPtr fakeHdr; rv = mDatabase->CreateNewHdr(msgKey, getter_AddRefs(fakeHdr)); NS_ENSURE_SUCCESS(rv, rv); fakeHdr->SetUint32Property("pseudoHdr", 1); // Should we add this to the offline store? nsCOMPtr offlineStore; if (storeOffline) { rv = GetOfflineStoreOutputStream(fakeHdr, getter_AddRefs(offlineStore)); NS_ENSURE_SUCCESS(rv, rv); } // We set an offline kMoveResult because in any case we want to update this // msgHdr with one downloaded from the server, with possible additional // headers added. nsCOMPtr op; nsCOMPtr opsDb = do_QueryInterface(mDatabase, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = opsDb->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op)); if (NS_SUCCEEDED(rv) && op) { nsCString destFolderUri; GetURI(destFolderUri); op->SetOperation(nsIMsgOfflineImapOperation::kMoveResult); op->SetDestinationFolderURI(destFolderUri); SetFlag(nsMsgFolderFlags::OfflineEvents); } nsCOMPtr inputStream; nsCOMPtr msgParser = do_CreateInstance("@mozilla.org/messenger/messagestateparser;1", &rv); NS_ENSURE_SUCCESS(rv, rv); msgParser->SetMailDB(mDatabase); rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), srcFile); if (NS_SUCCEEDED(rv) && inputStream) { // Now, parse the temp file to (optionally) copy to // the offline store for the cur folder. RefPtr inputStreamBuffer = new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false); int64_t fileSize; srcFile->GetFileSize(&fileSize); uint32_t bytesWritten; rv = NS_OK; msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState); msgParser->SetNewMsgHdr(fakeHdr); bool needMoreData = false; char* newLine = nullptr; uint32_t numBytesInLine = 0; if (offlineStore) { const char* envelope = "From " CRLF; offlineStore->Write(envelope, strlen(envelope), &bytesWritten); fileSize += bytesWritten; } do { newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine, needMoreData); if (newLine) { msgParser->ParseAFolderLine(newLine, numBytesInLine); if (offlineStore) rv = offlineStore->Write(newLine, numBytesInLine, &bytesWritten); free(newLine); NS_ENSURE_SUCCESS(rv, rv); } } while (newLine); msgParser->FinishHeader(); uint32_t resultFlags; if (offlineStore) fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read, &resultFlags); else fakeHdr->OrFlags(nsMsgMessageFlags::Read, &resultFlags); if (offlineStore) fakeHdr->SetOfflineMessageSize(fileSize); mDatabase->AddNewHdrToDB(fakeHdr, true /* notify */); // Call FinishNewMessage before setting pending attributes, as in // maildir it copies from tmp to cur and may change the storeToken // to get a unique filename. if (offlineStore) { nsCOMPtr msgStore; GetMsgStore(getter_AddRefs(msgStore)); if (msgStore) msgStore->FinishNewMessage(offlineStore, fakeHdr); } // We are copying from a file to offline store so set offline flag. SetPendingAttributes({&*fakeHdr}, false, true); // Gloda needs this notification to index the fake message. nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) notifier->NotifyMsgsClassified({&*fakeHdr}, false, false); inputStream->Close(); inputStream = nullptr; } if (offlineStore) offlineStore->Close(); return rv; } nsresult nsImapMailFolder::OnCopyCompleted(nsISupports* srcSupport, nsresult rv) { // if it's a file, and the copy succeeded, then fcc the offline // store, and add a kMoveResult offline op. if (NS_SUCCEEDED(rv) && m_copyState) { nsCOMPtr srcFile(do_QueryInterface(srcSupport)); if (srcFile) (void)CopyFileToOfflineStore(srcFile, m_copyState->m_appendUID); } m_copyState = nullptr; nsresult result; nsCOMPtr copyService = do_GetService("@mozilla.org/messenger/messagecopyservice;1", &result); NS_ENSURE_SUCCESS(result, result); return copyService->NotifyCompletion(srcSupport, this, rv); } nsresult nsImapMailFolder::CreateBaseMessageURI(const nsACString& aURI) { return nsCreateImapBaseMessageURI(aURI, mBaseMessageURI); } NS_IMETHODIMP nsImapMailFolder::GetFolderURL(nsACString& aFolderURL) { nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); rootFolder->GetURI(aFolderURL); if (rootFolder == this) return NS_OK; NS_ASSERTION(mURI.Length() > aFolderURL.Length(), "Should match with a folder name!"); nsCString escapedName; MsgEscapeString(Substring(mURI, aFolderURL.Length()), nsINetUtil::ESCAPE_URL_PATH, escapedName); if (escapedName.IsEmpty()) return NS_ERROR_OUT_OF_MEMORY; aFolderURL.Append(escapedName); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsSubscribing(bool* bVal) { NS_ENSURE_ARG_POINTER(bVal); *bVal = m_folderNeedsSubscribing; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsSubscribing(bool bVal) { m_folderNeedsSubscribing = bVal; return NS_OK; } nsMsgIMAPFolderACL* nsImapMailFolder::GetFolderACL() { if (!m_folderACL) m_folderACL = new nsMsgIMAPFolderACL(this); return m_folderACL; } nsresult nsImapMailFolder::CreateACLRightsStringForFolder( nsAString& rightsString) { GetFolderACL(); // lazy create NS_ENSURE_TRUE(m_folderACL, NS_ERROR_NULL_POINTER); return m_folderACL->CreateACLRightsString(rightsString); } NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsACLListed(bool* bVal) { NS_ENSURE_ARG_POINTER(bVal); bool dontNeedACLListed = !m_folderNeedsACLListed; // if we haven't acl listed, and it's not a no select folder or the inbox, // then we'll list the acl if it's not a namespace. if (m_folderNeedsACLListed && !(mFlags & (nsMsgFolderFlags::ImapNoselect | nsMsgFolderFlags::Inbox))) GetIsNamespace(&dontNeedACLListed); *bVal = !dontNeedACLListed; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsACLListed(bool bVal) { m_folderNeedsACLListed = bVal; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetIsNamespace(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); nsresult rv = NS_OK; if (!m_namespace) { #ifdef DEBUG_bienvenu // Make sure this isn't causing us to open the database NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown, "hierarchy delimiter not set"); #endif nsCString onlineName, serverKey; GetServerKey(serverKey); GetOnlineName(onlineName); char hierarchyDelimiter; GetHierarchyDelimiter(&hierarchyDelimiter); nsCOMPtr hostSession = do_GetService(kCImapHostSessionList, &rv); NS_ENSURE_SUCCESS(rv, rv); m_namespace = nsImapNamespaceList::GetNamespaceForFolder( serverKey.get(), onlineName.get(), hierarchyDelimiter); if (m_namespace == nullptr) { if (mFlags & nsMsgFolderFlags::ImapOtherUser) rv = hostSession->GetDefaultNamespaceOfTypeForHost( serverKey.get(), kOtherUsersNamespace, m_namespace); else if (mFlags & nsMsgFolderFlags::ImapPublic) rv = hostSession->GetDefaultNamespaceOfTypeForHost( serverKey.get(), kPublicNamespace, m_namespace); else rv = hostSession->GetDefaultNamespaceOfTypeForHost( serverKey.get(), kPersonalNamespace, m_namespace); } NS_ASSERTION(m_namespace, "failed to get namespace"); if (m_namespace) { nsImapNamespaceList::SuggestHierarchySeparatorForNamespace( m_namespace, hierarchyDelimiter); m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace( serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace); } } *aResult = m_folderIsNamespace; return rv; } NS_IMETHODIMP nsImapMailFolder::SetIsNamespace(bool isNamespace) { m_folderIsNamespace = isNamespace; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::ResetNamespaceReferences() { nsCString serverKey; nsCString onlineName; GetServerKey(serverKey); GetOnlineName(onlineName); char hierarchyDelimiter; GetHierarchyDelimiter(&hierarchyDelimiter); m_namespace = nsImapNamespaceList::GetNamespaceForFolder( serverKey.get(), onlineName.get(), hierarchyDelimiter); m_folderIsNamespace = m_namespace ? nsImapNamespaceList::GetFolderIsNamespace( serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace) : false; nsTArray> subFolders; nsresult rv = GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); for (nsIMsgFolder* f : subFolders) { nsCOMPtr imapFolder(do_QueryInterface(f, &rv)); NS_ENSURE_SUCCESS(rv, rv); rv = imapFolder->ResetNamespaceReferences(); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::FindOnlineSubFolder( const nsACString& targetOnlineName, nsIMsgImapMailFolder** aResultFolder) { *aResultFolder = nullptr; nsresult rv = NS_OK; nsCString onlineName; GetOnlineName(onlineName); if (onlineName.Equals(targetOnlineName)) { return QueryInterface(NS_GET_IID(nsIMsgImapMailFolder), (void**)aResultFolder); } nsTArray> subFolders; rv = GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); for (nsIMsgFolder* f : subFolders) { nsCOMPtr imapFolder(do_QueryInterface(f, &rv)); NS_ENSURE_SUCCESS(rv, rv); rv = imapFolder->FindOnlineSubFolder(targetOnlineName, aResultFolder); NS_ENSURE_SUCCESS(rv, rv); if (*aResultFolder) { return NS_OK; // Found it! } } return NS_OK; // Not found. } NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsAdded(bool* bVal) { NS_ENSURE_ARG_POINTER(bVal); *bVal = m_folderNeedsAdded; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsAdded(bool bVal) { m_folderNeedsAdded = bVal; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetFolderQuotaCommandIssued(bool* aCmdIssued) { NS_ENSURE_ARG_POINTER(aCmdIssued); *aCmdIssued = m_folderQuotaCommandIssued; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaCommandIssued(bool aCmdIssued) { m_folderQuotaCommandIssued = aCmdIssued; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaData( uint32_t aAction, const nsACString& aFolderQuotaRoot, uint64_t aFolderQuotaUsage, uint64_t aFolderQuotaLimit) { switch (aAction) { case kInvalidateQuota: // Reset to initialize evaluation of a new quotaroot imap response. This // clears any previous array data and marks the quota data for this folder // invalid. m_folderQuotaDataIsValid = false; m_folderQuota.Clear(); break; case kStoreQuota: // Store folder's quota data to an array. This will occur zero or more // times for a folder. m_folderQuota.AppendElement(new nsMsgQuota( aFolderQuotaRoot, aFolderQuotaUsage, aFolderQuotaLimit)); break; case kValidateQuota: // GETQUOTAROOT command was successful and OK response has occurred. This // indicates that all the untagged QUOTA responses have occurred so mark // as valid. m_folderQuotaDataIsValid = true; break; default: // Called with undefined aAction parameter. NS_ASSERTION(false, "undefined action"); } return NS_OK; } // Provide the quota array for status bar notification. NS_IMETHODIMP nsImapMailFolder::GetQuota( nsTArray>& aArray) { if (m_folderQuotaDataIsValid) { aArray = m_folderQuota.Clone(); } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::PerformExpand(nsIMsgWindow* aMsgWindow) { nsresult rv; bool usingSubscription = false; nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); rv = imapServer->GetUsingSubscription(&usingSubscription); if (NS_SUCCEEDED(rv) && !usingSubscription) { nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = imapService->DiscoverChildren(this, this, m_onlineFolderName); } return rv; } NS_IMETHODIMP nsImapMailFolder::RenameClient(nsIMsgWindow* msgWindow, nsIMsgFolder* msgFolder, const nsACString& oldName, const nsACString& newName) { nsresult rv; nsCOMPtr pathFile; rv = GetFilePath(getter_AddRefs(pathFile)); if (NS_FAILED(rv)) return rv; nsCOMPtr oldImapFolder = do_QueryInterface(msgFolder, &rv); if (NS_FAILED(rv)) return rv; char hierarchyDelimiter = '/'; oldImapFolder->GetHierarchyDelimiter(&hierarchyDelimiter); int32_t boxflags = 0; oldImapFolder->GetBoxFlags(&boxflags); nsAutoString newLeafName; NS_ConvertUTF8toUTF16 newNameString(newName); NS_ENSURE_SUCCESS(rv, rv); newLeafName = newNameString; nsAutoString folderNameStr; int32_t folderStart = newLeafName.RFindChar( '/'); // internal use of hierarchyDelimiter is always '/' if (folderStart > 0) { newLeafName = Substring(newNameString, folderStart + 1); CreateDirectoryForFolder( getter_AddRefs(pathFile)); // needed when we move a folder to a folder // with no subfolders. } // if we get here, it's really a leaf, and "this" is the parent. folderNameStr = newLeafName; // Create an empty database for this mail folder, set its name from the user nsCOMPtr mailDBFactory; nsCOMPtr child; nsCOMPtr imapFolder; nsCOMPtr msgDBService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr unusedDB; nsCOMPtr dbFile; // warning, path will be changed rv = CreateFileForDB(folderNameStr, pathFile, getter_AddRefs(dbFile)); NS_ENSURE_SUCCESS(rv, rv); // Use openMailDBFromFile() and not OpenFolderDB() here, since we don't use // the DB. rv = msgDBService->OpenMailDBFromFile(dbFile, nullptr, true, true, getter_AddRefs(unusedDB)); if (NS_SUCCEEDED(rv) && unusedDB) { // need to set the folder name nsCOMPtr folderInfo; rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); // Now let's create the actual new folder rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child)); if (!child || NS_FAILED(rv)) return rv; nsAutoString unicodeName; rv = CopyFolderNameToUTF16(NS_ConvertUTF16toUTF8(folderNameStr), unicodeName); if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName); imapFolder = do_QueryInterface(child); if (imapFolder) { nsAutoCString onlineName(m_onlineFolderName); if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter); onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr)); imapFolder->SetVerifiedAsOnlineFolder(true); imapFolder->SetOnlineName(onlineName); imapFolder->SetHierarchyDelimiter(hierarchyDelimiter); imapFolder->SetBoxFlags(boxflags); // store the online name as the mailbox name in the db folder info // I don't think anyone uses the mailbox name, so we'll use it // to restore the online name when blowing away an imap db. if (folderInfo) { nsAutoString unicodeOnlineName; CopyUTF8toUTF16(onlineName, unicodeOnlineName); folderInfo->SetMailboxName(unicodeOnlineName); } bool changed = false; msgFolder->MatchOrChangeFilterDestination( child, false /*caseInsensitive*/, &changed); if (changed) msgFolder->AlertFilterChanged(msgWindow); } unusedDB->SetSummaryValid(true); unusedDB->Commit(nsMsgDBCommitType::kLargeCommit); unusedDB->Close(true); child->RenameSubFolders(msgWindow, msgFolder); nsCOMPtr msgParent; msgFolder->GetParent(getter_AddRefs(msgParent)); msgFolder->SetParent(nullptr); // Reset online status now that the folder is renamed. nsCOMPtr oldImapFolder = do_QueryInterface(msgFolder); if (oldImapFolder) oldImapFolder->SetVerifiedAsOnlineFolder(false); nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); if (notifier) notifier->NotifyFolderRenamed(msgFolder, child); // Do not propagate the deletion until after we have (synchronously) // notified all listeners about the rename. This allows them to access // properties on the source folder without experiencing failures. if (msgParent) msgParent->PropagateDelete(msgFolder, true); NotifyFolderAdded(child); } return rv; } NS_IMETHODIMP nsImapMailFolder::RenameSubFolders(nsIMsgWindow* msgWindow, nsIMsgFolder* oldFolder) { m_initialized = true; nsTArray> subFolders; nsresult rv = oldFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); for (nsIMsgFolder* msgFolder : subFolders) { nsCOMPtr folder(do_QueryInterface(msgFolder, &rv)); NS_ENSURE_SUCCESS(rv, rv); char hierarchyDelimiter = '/'; folder->GetHierarchyDelimiter(&hierarchyDelimiter); int32_t boxflags; folder->GetBoxFlags(&boxflags); bool verified; folder->GetVerifiedAsOnlineFolder(&verified); nsCOMPtr oldPathFile; rv = msgFolder->GetFilePath(getter_AddRefs(oldPathFile)); if (NS_FAILED(rv)) return rv; nsCOMPtr newParentPathFile; rv = GetFilePath(getter_AddRefs(newParentPathFile)); if (NS_FAILED(rv)) return rv; rv = AddDirectorySeparator(newParentPathFile); nsAutoCString oldLeafName; oldPathFile->GetNativeLeafName(oldLeafName); newParentPathFile->AppendNative(oldLeafName); nsCOMPtr newPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); newPathFile->InitWithFile(newParentPathFile); nsCOMPtr dbFilePath = newPathFile; nsCOMPtr child; nsString folderName; rv = msgFolder->GetName(folderName); if (folderName.IsEmpty() || NS_FAILED(rv)) return rv; nsCString utfLeafName; bool utf8AcceptEnabled; nsCOMPtr imapFolder = do_QueryInterface(msgFolder); rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled); NS_ENSURE_SUCCESS(rv, rv); if (utf8AcceptEnabled) { CopyUTF16toUTF8(folderName, utfLeafName); } else { CopyUTF16toMUTF7(folderName, utfLeafName); } // XXX : Fix this non-sense by fixing AddSubfolderWithPath nsAutoString unicodeLeafName; CopyUTF8toUTF16(utfLeafName, unicodeLeafName); rv = AddSubfolderWithPath(unicodeLeafName, dbFilePath, getter_AddRefs(child)); if (!child || NS_FAILED(rv)) return rv; child->SetName(folderName); imapFolder = do_QueryInterface(child); nsCString onlineName; GetOnlineName(onlineName); nsAutoCString onlineCName(onlineName); onlineCName.Append(hierarchyDelimiter); onlineCName.Append(utfLeafName); if (imapFolder) { imapFolder->SetVerifiedAsOnlineFolder(verified); imapFolder->SetOnlineName(onlineCName); imapFolder->SetHierarchyDelimiter(hierarchyDelimiter); imapFolder->SetBoxFlags(boxflags); bool changed = false; msgFolder->MatchOrChangeFilterDestination( child, false /*caseInsensitive*/, &changed); if (changed) msgFolder->AlertFilterChanged(msgWindow); child->RenameSubFolders(msgWindow, msgFolder); } } return rv; } NS_IMETHODIMP nsImapMailFolder::IsCommandEnabled(const nsACString& command, bool* result) { NS_ENSURE_ARG_POINTER(result); *result = !(WeAreOffline() && (command.EqualsLiteral("cmd_renameFolder") || command.EqualsLiteral("cmd_compactFolder") || command.EqualsLiteral("button_compact") || command.EqualsLiteral("cmd_delete") || command.EqualsLiteral("button_delete"))); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetCanFileMessages(bool* aCanFileMessages) { nsresult rv; *aCanFileMessages = true; nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) rv = server->GetCanFileMessagesOnServer(aCanFileMessages); if (*aCanFileMessages) rv = nsMsgDBFolder::GetCanFileMessages(aCanFileMessages); if (*aCanFileMessages) { bool noSelect; GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect); *aCanFileMessages = (noSelect) ? false : GetFolderACL()->GetCanIInsertInFolder(); return NS_OK; } return rv; } NS_IMETHODIMP nsImapMailFolder::GetCanDeleteMessages(bool* aCanDeleteMessages) { NS_ENSURE_ARG_POINTER(aCanDeleteMessages); *aCanDeleteMessages = GetFolderACL()->GetCanIDeleteInFolder(); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetPerformingBiff(bool* aPerformingBiff) { NS_ENSURE_ARG_POINTER(aPerformingBiff); *aPerformingBiff = m_performingBiff; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetPerformingBiff(bool aPerformingBiff) { m_performingBiff = aPerformingBiff; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::SetFilterList(nsIMsgFilterList* aMsgFilterList) { m_filterList = aMsgFilterList; return nsMsgDBFolder::SetFilterList(aMsgFilterList); } nsresult nsImapMailFolder::GetMoveCoalescer() { if (!m_moveCoalescer) m_moveCoalescer = new nsImapMoveCoalescer(this, nullptr /* msgWindow */); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow* aMsgWindow, const nsACString& aFlagsToAdd, const nsACString& aFlagsToSubtract, const nsTArray& aKeysToStore, nsIURI** _retval) { if (aKeysToStore.IsEmpty()) return NS_OK; nsresult rv = NS_OK; if (WeAreOffline()) { GetDatabase(); if (!mDatabase) return NS_ERROR_UNEXPECTED; nsCOMPtr opsDb = do_QueryInterface(mDatabase, &rv); NS_ENSURE_SUCCESS(rv, rv); for (auto key : aKeysToStore) { nsCOMPtr op; nsresult rv2 = opsDb->GetOfflineOpForKey(key, true, getter_AddRefs(op)); if (NS_FAILED(rv2)) rv = rv2; SetFlag(nsMsgFolderFlags::OfflineEvents); if (NS_SUCCEEDED(rv2) && op) { if (!aFlagsToAdd.IsEmpty()) op->AddKeywordToAdd(PromiseFlatCString(aFlagsToAdd).get()); if (!aFlagsToSubtract.IsEmpty()) op->AddKeywordToRemove(PromiseFlatCString(aFlagsToSubtract).get()); } } mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline ops return rv; } nsCOMPtr imapService( do_GetService("@mozilla.org/messenger/imapservice;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString msgIds; AllocateUidStringFromKeys(aKeysToStore, msgIds); nsCOMPtr retUri; rv = imapService->StoreCustomKeywords(this, aMsgWindow, aFlagsToAdd, aFlagsToSubtract, msgIds, getter_AddRefs(retUri)); if (_retval) { retUri.forget(_retval); } return rv; } NS_IMETHODIMP nsImapMailFolder::NotifyIfNewMail() { return PerformBiffNotifications(); } bool nsImapMailFolder::ShowPreviewText() { bool showPreviewText = false; nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefBranch) prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText); return showPreviewText; } nsresult nsImapMailFolder::PlaybackCoalescedOperations() { if (m_moveCoalescer) { nsTArray* junkKeysToClassify = m_moveCoalescer->GetKeyBucket(0); if (junkKeysToClassify && !junkKeysToClassify->IsEmpty()) StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "Junk"_ns, EmptyCString(), *junkKeysToClassify, nullptr); junkKeysToClassify->Clear(); nsTArray* nonJunkKeysToClassify = m_moveCoalescer->GetKeyBucket(1); if (nonJunkKeysToClassify && !nonJunkKeysToClassify->IsEmpty()) StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "NonJunk"_ns, EmptyCString(), *nonJunkKeysToClassify, nullptr); nonJunkKeysToClassify->Clear(); return m_moveCoalescer->PlaybackMoves(ShowPreviewText()); } return NS_OK; // must not be any coalesced operations } NS_IMETHODIMP nsImapMailFolder::SetJunkScoreForMessages( const nsTArray>& aMessages, const nsACString& aJunkScore) { nsresult rv = nsMsgDBFolder::SetJunkScoreForMessages(aMessages, aJunkScore); if (NS_SUCCEEDED(rv)) { nsAutoCString messageIds; nsTArray keys; nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys); NS_ENSURE_SUCCESS(rv, rv); StoreCustomKeywords( nullptr, aJunkScore.EqualsLiteral("0") ? "NonJunk"_ns : "Junk"_ns, aJunkScore.EqualsLiteral("0") ? "Junk"_ns : "NonJunk"_ns, keys, nullptr); if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } return rv; } NS_IMETHODIMP nsImapMailFolder::OnMessageClassified(const nsACString& aMsgURI, nsMsgJunkStatus aClassification, uint32_t aJunkPercent) { nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); if (!aMsgURI.IsEmpty()) // not end of batch { nsCOMPtr msgHdr; rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey msgKey; rv = msgHdr->GetMessageKey(&msgKey); NS_ENSURE_SUCCESS(rv, rv); // check if this message needs junk classification uint32_t processingFlags; GetProcessingFlags(msgKey, &processingFlags); if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) { nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification, aJunkPercent); GetMoveCoalescer(); if (m_moveCoalescer) { nsTArray* keysToClassify = m_moveCoalescer->GetKeyBucket( (aClassification == nsIJunkMailPlugin::JUNK) ? 0 : 1); NS_ASSERTION(keysToClassify, "error getting key bucket"); if (keysToClassify) keysToClassify->AppendElement(msgKey); } if (aClassification == nsIJunkMailPlugin::JUNK) { nsCOMPtr spamSettings; rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); NS_ENSURE_SUCCESS(rv, rv); bool markAsReadOnSpam; (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam); if (markAsReadOnSpam) { m_junkMessagesToMarkAsRead.AppendElement(msgHdr); } bool willMoveMessage = false; // don't do the move when we are opening up // the junk mail folder or the trash folder // or when manually classifying messages in those folders if (!(mFlags & nsMsgFolderFlags::Junk || mFlags & nsMsgFolderFlags::Trash)) { bool moveOnSpam; (void)spamSettings->GetMoveOnSpam(&moveOnSpam); if (moveOnSpam) { nsCString spamFolderURI; rv = spamSettings->GetSpamFolderURI(spamFolderURI); NS_ENSURE_SUCCESS(rv, rv); if (!spamFolderURI.IsEmpty()) { rv = FindFolder(spamFolderURI, getter_AddRefs(mSpamFolder)); NS_ENSURE_SUCCESS(rv, rv); if (mSpamFolder) { rv = mSpamFolder->SetFlag(nsMsgFolderFlags::Junk); NS_ENSURE_SUCCESS(rv, rv); mSpamKeysToMove.AppendElement(msgKey); willMoveMessage = true; } else { // XXX TODO // JUNK MAIL RELATED // the listener should do // rv = folder->SetFlag(nsMsgFolderFlags::Junk); // NS_ENSURE_SUCCESS(rv,rv); // if (NS_SUCCEEDED(GetMoveCoalescer())) { // m_moveCoalescer->AddMove(folder, msgKey); // willMoveMessage = true; // } rv = GetOrCreateJunkFolder(spamFolderURI, nullptr /* aListener */); NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed"); } } } } rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage); NS_ENSURE_SUCCESS(rv, rv); } } } else // end of batch { // Parent will apply post bayes filters. nsMsgDBFolder::OnMessageClassified(EmptyCString(), nsIJunkMailPlugin::UNCLASSIFIED, 0); if (!m_junkMessagesToMarkAsRead.IsEmpty()) { rv = MarkMessagesRead(m_junkMessagesToMarkAsRead, true); NS_ENSURE_SUCCESS(rv, rv); m_junkMessagesToMarkAsRead.Clear(); } if (!mSpamKeysToMove.IsEmpty()) { GetMoveCoalescer(); for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length(); keyIndex++) { // If an upstream filter moved this message, don't move it here. nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex); nsMsgProcessingFlagType processingFlags; GetProcessingFlags(msgKey, &processingFlags); if (!(processingFlags & nsMsgProcessingFlags::FilterToMove)) { if (m_moveCoalescer && mSpamFolder) m_moveCoalescer->AddMove(mSpamFolder, msgKey); } else { // We don't need the FilterToMove flag anymore. AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove); } } mSpamKeysToMove.Clear(); } // Let's not hold onto the spam folder reference longer than necessary. mSpamFolder = nullptr; bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves(); PlaybackCoalescedOperations(); // If we are performing biff for this folder, tell the server object if ((!pendingMoves || !ShowPreviewText()) && m_performingBiff) { // we don't need to adjust the num new messages in this folder because // the playback moves code already did that. (void)PerformBiffNotifications(); server->SetPerformingBiff(false); m_performingBiff = false; } } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetShouldDownloadAllHeaders(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = false; // for just the inbox, we check if the filter list has arbitrary headers. // for all folders, check if we have a spam plugin that requires all headers if (mFlags & nsMsgFolderFlags::Inbox) { nsCOMPtr filterList; nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList)); NS_ENSURE_SUCCESS(rv, rv); rv = filterList->GetShouldDownloadAllHeaders(aResult); if (*aResult) return rv; } nsCOMPtr filterPlugin; nsCOMPtr server; if (NS_SUCCEEDED(GetServer(getter_AddRefs(server)))) server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin)); return (filterPlugin) ? filterPlugin->GetShouldDownloadAllHeaders(aResult) : NS_OK; } void nsImapMailFolder::GetTrashFolderName(nsAString& aFolderName) { nsCOMPtr server; nsCOMPtr imapServer; nsresult rv; rv = GetServer(getter_AddRefs(server)); if (NS_FAILED(rv)) return; imapServer = do_QueryInterface(server, &rv); if (NS_FAILED(rv)) return; imapServer->GetTrashFolderName(aFolderName); return; } NS_IMETHODIMP nsImapMailFolder::FetchMsgPreviewText( nsTArray const& aKeysToFetch, nsIUrlListener* aUrlListener, bool* aAsyncResults) { NS_ENSURE_ARG_POINTER(aAsyncResults); nsTArray keysToFetchFromServer; *aAsyncResults = false; nsresult rv = NS_OK; nsCOMPtr msgService = do_GetService("@mozilla.org/messenger/messageservice;1?type=imap", &rv); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) { nsCOMPtr msgHdr; nsCString prevBody; rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); // ignore messages that already have a preview body. msgHdr->GetStringProperty("preview", prevBody); if (!prevBody.IsEmpty()) continue; /* check if message is in memory cache or offline store. */ nsCOMPtr url; nsCOMPtr inputStream; nsCString messageUri; rv = GetUriForMsg(msgHdr, messageUri); NS_ENSURE_SUCCESS(rv, rv); rv = msgService->GetUrlForUri(messageUri, nullptr, getter_AddRefs(url)); NS_ENSURE_SUCCESS(rv, rv); // Lets look in the offline store. uint32_t msgFlags; msgHdr->GetFlags(&msgFlags); if (msgFlags & nsMsgMessageFlags::Offline) { rv = GetLocalMsgStream(msgHdr, getter_AddRefs(inputStream)); NS_ENSURE_SUCCESS(rv, rv); rv = GetMsgPreviewTextFromStream(msgHdr, inputStream); NS_ENSURE_SUCCESS(rv, rv); } else { nsMsgKey msgKey; msgHdr->GetMessageKey(&msgKey); keysToFetchFromServer.AppendElement(msgKey); } } if (!keysToFetchFromServer.IsEmpty()) { uint32_t msgCount = keysToFetchFromServer.Length(); nsAutoCString messageIds; AllocateImapUidString(keysToFetchFromServer.Elements(), msgCount, nullptr, messageIds); nsCOMPtr imapService = do_GetService("@mozilla.org/messenger/imapservice;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr outUri; rv = imapService->GetBodyStart(this, aUrlListener, messageIds, 2048, getter_AddRefs(outUri)); *aAsyncResults = true; // the preview text will be available async... } return NS_OK; } NS_IMETHODIMP nsImapMailFolder::AddKeywordsToMessages( const nsTArray>& aMessages, const nsACString& aKeywords) { nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords); if (NS_SUCCEEDED(rv)) { nsAutoCString messageIds; nsTArray keys; rv = BuildIdsAndKeyArray(aMessages, messageIds, keys); NS_ENSURE_SUCCESS(rv, rv); rv = StoreCustomKeywords(nullptr, aKeywords, EmptyCString(), keys, nullptr); if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } return rv; } NS_IMETHODIMP nsImapMailFolder::RemoveKeywordsFromMessages( const nsTArray>& aMessages, const nsACString& aKeywords) { nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords); if (NS_SUCCEEDED(rv)) { nsAutoCString messageIds; nsTArray keys; nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys); NS_ENSURE_SUCCESS(rv, rv); rv = StoreCustomKeywords(nullptr, EmptyCString(), aKeywords, keys, nullptr); if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } return rv; } NS_IMETHODIMP nsImapMailFolder::GetCustomIdentity(nsIMsgIdentity** aIdentity) { NS_ENSURE_ARG_POINTER(aIdentity); if (mFlags & nsMsgFolderFlags::ImapOtherUser) { nsresult rv; bool delegateOtherUsersFolders = false; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); prefBranch->GetBoolPref("mail.imap.delegateOtherUsersFolders", &delegateOtherUsersFolders); // if we're automatically delegating other user's folders, we need to // cons up an e-mail address for the other user. We do that by // taking the other user's name and the current user's domain name, // assuming they'll be the same. So, @ if (delegateOtherUsersFolders) { nsCOMPtr server = do_QueryReferent(mServer, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr accountManager = do_GetService("@mozilla.org/messenger/account-manager;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr ourIdentity; nsCOMPtr retIdentity; nsCOMPtr account; nsCString foldersUserName; nsCString ourEmailAddress; accountManager->FindAccountForServer(server, getter_AddRefs(account)); NS_ENSURE_SUCCESS(rv, rv); account->GetDefaultIdentity(getter_AddRefs(ourIdentity)); NS_ENSURE_SUCCESS(rv, rv); ourIdentity->GetEmail(ourEmailAddress); int32_t atPos = ourEmailAddress.FindChar('@'); if (atPos != kNotFound) { nsCString otherUsersEmailAddress; GetFolderOwnerUserName(otherUsersEmailAddress); otherUsersEmailAddress.Append( Substring(ourEmailAddress, atPos, ourEmailAddress.Length())); nsTArray> identities; rv = accountManager->GetIdentitiesForServer(server, identities); NS_ENSURE_SUCCESS(rv, rv); for (auto identity : identities) { if (!identity) continue; nsCString identityEmail; identity->GetEmail(identityEmail); if (identityEmail.Equals(otherUsersEmailAddress)) { retIdentity = identity; break; } } if (!retIdentity) { // create the identity rv = accountManager->CreateIdentity(getter_AddRefs(retIdentity)); NS_ENSURE_SUCCESS(rv, rv); retIdentity->SetEmail(otherUsersEmailAddress); nsCOMPtr account; accountManager->FindAccountForServer(server, getter_AddRefs(account)); NS_ENSURE_SUCCESS(rv, rv); account->AddIdentity(retIdentity); } } if (retIdentity) { retIdentity.forget(aIdentity); return NS_OK; } } } return nsMsgDBFolder::GetCustomIdentity(aIdentity); } NS_IMETHODIMP nsImapMailFolder::ChangePendingTotal(int32_t aDelta) { ChangeNumPendingTotalMessages(aDelta); if (aDelta > 0) NotifyHasPendingMsgs(); return NS_OK; } void nsImapMailFolder::NotifyHasPendingMsgs() { InitAutoSyncState(); nsresult rv; nsCOMPtr autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) autoSyncMgr->OnFolderHasPendingMsgs(m_autoSyncStateObj); } /* void changePendingUnread (in long aDelta); */ NS_IMETHODIMP nsImapMailFolder::ChangePendingUnread(int32_t aDelta) { ChangeNumPendingUnread(aDelta); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetServerRecent(int32_t* aServerRecent) { NS_ENSURE_ARG_POINTER(aServerRecent); *aServerRecent = m_numServerRecentMessages; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetServerTotal(int32_t* aServerTotal) { NS_ENSURE_ARG_POINTER(aServerTotal); *aServerTotal = m_numServerTotalMessages; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetServerUnseen(int32_t* aServerUnseen) { NS_ENSURE_ARG_POINTER(aServerUnseen); *aServerUnseen = m_numServerUnseenMessages; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetServerNextUID(int32_t* aNextUID) { NS_ENSURE_ARG_POINTER(aNextUID); *aNextUID = m_nextUID; return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetAutoSyncStateObj( nsIAutoSyncState** autoSyncStateObj) { NS_ENSURE_ARG_POINTER(autoSyncStateObj); // create auto-sync state object lazily InitAutoSyncState(); NS_IF_ADDREF(*autoSyncStateObj = m_autoSyncStateObj); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::InitiateAutoSync(nsIUrlListener* aUrlListener) { nsCString folderName; GetURI(folderName); MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("%s: Updating folder: %s", __func__, folderName.get())); // HACK: if UpdateFolder finds out that it can't open // the folder, it doesn't set the url listener and returns // no error. In this case, we return success from this call // but the caller never gets a notification on its url listener. bool canOpenThisFolder = true; GetCanOpenFolder(&canOpenThisFolder); if (!canOpenThisFolder) { MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug, ("%s: Cannot update folder: %s", __func__, folderName.get())); return NS_ERROR_FAILURE; } // create auto-sync state object lazily InitAutoSyncState(); // make sure we get the counts from the folder cache. ReadDBFolderInfo(false); nsresult rv = m_autoSyncStateObj->ManageStorageSpace(); NS_ENSURE_SUCCESS(rv, rv); int32_t syncState; m_autoSyncStateObj->GetState(&syncState); if (syncState == nsAutoSyncState::stUpdateNeeded) return m_autoSyncStateObj->UpdateFolder(); // We only want to init the autosyncStateObj server counts the first time // we update, and update it when the STATUS call finishes. This deals with // the case where biff is doing a STATUS on a non-inbox folder, which // can make autosync think the counts aren't changing. PRTime lastUpdateTime; m_autoSyncStateObj->GetLastUpdateTime(&lastUpdateTime); if (!lastUpdateTime) m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages, m_numServerRecentMessages, m_numServerUnseenMessages, m_nextUID); // Issue a STATUS command and see if any counts changed. m_autoSyncStateObj->SetState(nsAutoSyncState::stStatusIssued); // The OnStopRunningUrl method of the autosync state obj // will check if the counts or next uid have changed, // and if so, will issue an UpdateFolder(). rv = UpdateStatus(m_autoSyncStateObj, nullptr); NS_ENSURE_SUCCESS(rv, rv); // record the last update time m_autoSyncStateObj->SetLastUpdateTime(PR_Now()); return NS_OK; } /* static */ void nsImapMailFolder::PlaybackTimerCallback(nsITimer* aTimer, void* aClosure) { nsPlaybackRequest* request = static_cast(aClosure); NS_ASSERTION(request->SrcFolder->m_pendingPlaybackReq == request, "wrong playback request pointer"); RefPtr offlineSync = new nsImapOfflineSync(); offlineSync->Init(request->MsgWindow, nullptr, request->SrcFolder, true); if (offlineSync) { mozilla::DebugOnly rv = offlineSync->ProcessNextOperation(); NS_ASSERTION(NS_SUCCEEDED(rv), "pseudo-offline playback is not successful"); } // release request struct and timer request->SrcFolder->m_pendingPlaybackReq = nullptr; request->SrcFolder->m_playbackTimer = nullptr; // Just to flag timed out delete request; } void nsImapMailFolder::InitAutoSyncState() { if (!m_autoSyncStateObj) m_autoSyncStateObj = new nsAutoSyncState(this); } NS_IMETHODIMP nsImapMailFolder::HasMsgOffline(nsMsgKey msgKey, bool* _retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = false; nsCOMPtr msgFolder; nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(msgFolder)); if (NS_SUCCEEDED(rv) && msgFolder) *_retval = true; return NS_OK; } nsresult nsImapMailFolder::GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder** aMsgFolder) { // Check if we have the message in the current folder. NS_ENSURE_ARG_POINTER(aMsgFolder); nsCOMPtr subMsgFolder; nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr hdr; rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); if (NS_FAILED(rv)) return rv; if (hdr) { uint32_t msgFlags = 0; hdr->GetFlags(&msgFlags); // Check if we already have this message body offline if ((msgFlags & nsMsgMessageFlags::Offline)) { NS_IF_ADDREF(*aMsgFolder = this); return NS_OK; } } if (!*aMsgFolder) { // Checking the existence of message in other folders in case of GMail // Server bool isGMail; nsCOMPtr imapServer; rv = GetImapIncomingServer(getter_AddRefs(imapServer)); NS_ENSURE_SUCCESS(rv, rv); rv = imapServer->GetIsGMailServer(&isGMail); NS_ENSURE_SUCCESS(rv, rv); if (isGMail) { nsCString labels; nsTArray labelNames; hdr->GetStringProperty("X-GM-LABELS", labels); ParseString(labels, ' ', labelNames); nsCOMPtr rootFolder; nsCOMPtr subFolder; for (uint32_t i = 0; i < labelNames.Length(); i++) { rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && (rootFolder)) { nsCOMPtr imapRootFolder = do_QueryInterface(rootFolder); if (labelNames[i].EqualsLiteral("\"\\\\Draft\"")) rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts, getter_AddRefs(subMsgFolder)); if (labelNames[i].EqualsLiteral("\"\\\\Inbox\"")) rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(subMsgFolder)); if (labelNames[i].EqualsLiteral("\"\\\\All Mail\"")) rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive, getter_AddRefs(subMsgFolder)); if (labelNames[i].EqualsLiteral("\"\\\\Trash\"")) rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, getter_AddRefs(subMsgFolder)); if (labelNames[i].EqualsLiteral("\"\\\\Spam\"")) rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk, getter_AddRefs(subMsgFolder)); if (labelNames[i].EqualsLiteral("\"\\\\Sent\"")) rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail, getter_AddRefs(subMsgFolder)); if (FindInReadable("[Imap]/"_ns, labelNames[i], nsCaseInsensitiveCStringComparator)) { labelNames[i].ReplaceSubstring("[Imap]/", ""); imapRootFolder->FindOnlineSubFolder(labelNames[i], getter_AddRefs(subFolder)); subMsgFolder = do_QueryInterface(subFolder); } if (!subMsgFolder) { imapRootFolder->FindOnlineSubFolder(labelNames[i], getter_AddRefs(subFolder)); subMsgFolder = do_QueryInterface(subFolder); } if (subMsgFolder) { nsCOMPtr db; subMsgFolder->GetMsgDatabase(getter_AddRefs(db)); if (db) { nsCOMPtr retHdr; nsCString gmMsgID; hdr->GetStringProperty("X-GM-MSGID", gmMsgID); rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(retHdr)); if (NS_FAILED(rv)) return rv; if (retHdr) { uint32_t gmFlags = 0; retHdr->GetFlags(&gmFlags); if ((gmFlags & nsMsgMessageFlags::Offline)) { subMsgFolder.forget(aMsgFolder); // Focus on first positive result. return NS_OK; } } } } } } } } return NS_OK; } nsresult nsImapMailFolder::GetOfflineFileStream(nsMsgKey msgKey, uint64_t* offset, uint32_t* size, nsIInputStream** aFileStream) { NS_ENSURE_ARG(aFileStream); nsCOMPtr offlineFolder; nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(offlineFolder)); NS_ENSURE_SUCCESS(rv, rv); if (!offlineFolder) return NS_ERROR_FAILURE; rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); if (offlineFolder == this) { return nsMsgDBFolder::GetOfflineFileStream(msgKey, offset, size, aFileStream); } // The message we want is stored in a different folder (hackery for gmail). nsCOMPtr hdr; rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); NS_ENSURE_SUCCESS(rv, rv); nsCString gmMsgID; hdr->GetStringProperty("X-GM-MSGID", gmMsgID); nsCOMPtr db; offlineFolder->GetMsgDatabase(getter_AddRefs(db)); rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr)); NS_ENSURE_SUCCESS(rv, rv); if (!hdr) { return NS_ERROR_FAILURE; } nsMsgKey newMsgKey; hdr->GetMessageKey(&newMsgKey); // We _know_ it's a nsImapMailFolder. nsImapMailFolder* other = static_cast(offlineFolder.get()); return other->GetOfflineFileStream(newMsgKey, offset, size, aFileStream); } NS_IMETHODIMP nsImapMailFolder::GetLocalMsgStream(nsIMsgDBHdr* hdr, nsIInputStream** stream) { nsMsgKey key; hdr->GetMessageKey(&key); uint64_t offset = 0; uint32_t size = 0; nsCOMPtr rawStream; nsresult rv = GetOfflineFileStream(key, &offset, &size, getter_AddRefs(rawStream)); NS_ENSURE_SUCCESS(rv, rv); RefPtr slicedStream = new SlicedInputStream(rawStream.forget(), offset, uint64_t(size)); slicedStream.forget(stream); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetIncomingServerType(nsACString& serverType) { serverType.AssignLiteral("imap"); return NS_OK; } NS_IMETHODIMP nsImapMailFolder::GetShouldUseUtf8FolderName(bool* aUseUTF8) { *aUseUTF8 = false; nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr imapServer = do_QueryInterface(server, &rv); NS_ENSURE_SUCCESS(rv, rv); imapServer->GetUtf8AcceptEnabled(aUseUTF8); return NS_OK; } void nsImapMailFolder::DeleteStoreMessages( const nsTArray>& aMessages) { // Delete messages for pluggable stores that do not support compaction. nsCOMPtr offlineStore; (void)GetMsgStore(getter_AddRefs(offlineStore)); if (offlineStore) { bool supportsCompaction; offlineStore->GetSupportsCompaction(&supportsCompaction); if (!supportsCompaction) offlineStore->DeleteMessages(aMessages); } } void nsImapMailFolder::DeleteStoreMessages( const nsTArray& aMessages) { DeleteStoreMessages(aMessages, this); } void nsImapMailFolder::DeleteStoreMessages(const nsTArray& aMessages, nsIMsgFolder* aFolder) { // Delete messages for pluggable stores that do not support compaction. NS_ASSERTION(aFolder, "Missing Source Folder"); nsCOMPtr offlineStore; (void)aFolder->GetMsgStore(getter_AddRefs(offlineStore)); if (offlineStore) { bool supportsCompaction; offlineStore->GetSupportsCompaction(&supportsCompaction); if (!supportsCompaction) { nsCOMPtr db; aFolder->GetMsgDatabase(getter_AddRefs(db)); nsresult rv = NS_ERROR_FAILURE; nsTArray> messages; if (db) rv = MsgGetHeadersFromKeys(db, aMessages, messages); if (NS_SUCCEEDED(rv)) offlineStore->DeleteMessages(messages); else NS_WARNING("Failed to get database"); } } }