diff options
Diffstat (limited to 'comm/mailnews/local/src/nsMsgMaildirStore.cpp')
-rw-r--r-- | comm/mailnews/local/src/nsMsgMaildirStore.cpp | 1380 |
1 files changed, 1380 insertions, 0 deletions
diff --git a/comm/mailnews/local/src/nsMsgMaildirStore.cpp b/comm/mailnews/local/src/nsMsgMaildirStore.cpp new file mode 100644 index 0000000000..2f597cd464 --- /dev/null +++ b/comm/mailnews/local/src/nsMsgMaildirStore.cpp @@ -0,0 +1,1380 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + Class for handling Maildir stores. +*/ + +#include "prprf.h" +#include "msgCore.h" +#include "nsMsgMaildirStore.h" +#include "nsIMsgFolder.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsISimpleEnumerator.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIInputStream.h" +#include "nsMsgFolderFlags.h" +#include "nsCOMArray.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsIMsgDatabase.h" +#include "nsNativeCharsetUtils.h" +#include "nsMsgUtils.h" +#include "nsIDBFolderInfo.h" +#include "nsMailHeaders.h" +#include "nsParseMailbox.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsITimer.h" +#include "nsIMailboxUrl.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgFilterPlugin.h" +#include "nsLocalUndoTxn.h" +#include "nsIMessenger.h" +#include "mozilla/Logging.h" +#include "mozilla/SlicedInputStream.h" +#include "mozilla/UniquePtr.h" + +static mozilla::LazyLogModule MailDirLog("MailDirStore"); + +// Helper function to produce a safe filename from a Message-ID value. +// We'll percent-encode anything not in this set: [-+.%=@_0-9a-zA-Z] +// This is an overly-picky set, but should: +// - leave most sane Message-IDs unchanged +// - be safe on windows (the pickiest case) +// - avoid chars that can trip up shell scripts (spaces, semicolons etc) +// If input contains malicious binary (or multibyte chars) it'll be +// safely encoded as individual bytes. +static void percentEncode(nsACString const& in, nsACString& out) { + const char* end = in.EndReading(); + const char* cur; + // We know the output will be at least as long as the input. + out.SetLength(0); + out.SetCapacity(in.Length()); + for (cur = in.BeginReading(); cur < end; ++cur) { + const char c = *cur; + bool whitelisted = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || c == '-' || c == '+' || + c == '.' || c == '%' || c == '=' || c == '@' || c == '_'; + if (whitelisted) { + out.Append(c); + } else { + out.AppendPrintf("%%%02x", (unsigned char)c); + } + } +} + +nsMsgMaildirStore::nsMsgMaildirStore() {} + +nsMsgMaildirStore::~nsMsgMaildirStore() {} + +NS_IMPL_ISUPPORTS(nsMsgMaildirStore, nsIMsgPluggableStore) + +// Iterates over the folders in the "path" directory, and adds subfolders to +// parent for each Maildir folder found. +nsresult nsMsgMaildirStore::AddSubFolders(nsIMsgFolder* parent, nsIFile* path, + bool deep) { + nsCOMArray<nsIFile> currentDirEntries; + + nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator; + nsresult rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsIFile> currentFile; + rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFile)); + if (NS_SUCCEEDED(rv) && currentFile) { + nsAutoString leafName; + currentFile->GetLeafName(leafName); + bool isDirectory = false; + currentFile->IsDirectory(&isDirectory); + // Make sure this really is a mail folder dir (i.e., a directory that + // contains cur and tmp sub-dirs, and not a .sbd or .mozmsgs dir). + if (isDirectory && !nsShouldIgnoreFile(leafName, currentFile)) + currentDirEntries.AppendObject(currentFile); + } + } + + // add the folders + int32_t count = currentDirEntries.Count(); + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]); + + nsAutoString leafName; + currentFile->GetLeafName(leafName); + + nsCOMPtr<nsIMsgFolder> child; + rv = parent->AddSubfolder(leafName, getter_AddRefs(child)); + if (child) { + nsString folderName; + child->GetName(folderName); // try to get it from cache/db + if (folderName.IsEmpty()) child->SetPrettyName(leafName); + if (deep) { + nsCOMPtr<nsIFile> path; + rv = child->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + // Construct the .sbd directory path for the possible children of the + // folder. + GetDirectoryForFolder(path); + bool directory = false; + // Check that <folder>.sbd really is a directory. + path->IsDirectory(&directory); + if (directory) AddSubFolders(child, path, true); + } + } + } + return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::DiscoverSubFolders(nsIMsgFolder* aParentFolder, + bool aDeep) { + NS_ENSURE_ARG_POINTER(aParentFolder); + + nsCOMPtr<nsIFile> path; + nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isServer, directory = false; + aParentFolder->GetIsServer(&isServer); + if (!isServer) GetDirectoryForFolder(path); + + path->IsDirectory(&directory); + if (directory) rv = AddSubFolders(aParentFolder, path, aDeep); + + return (rv == NS_MSG_FOLDER_EXISTS) ? NS_OK : rv; +} + +/** + * Create if missing a Maildir-style folder with "tmp" and "cur" subfolders + * but no "new" subfolder, because it doesn't make sense in the mail client + * context. ("new" directory is for messages on the server that haven't been + * seen by a mail client). + * aFolderName is already "safe" - it has been through NS_MsgHashIfNecessary. + */ +nsresult nsMsgMaildirStore::CreateMaildir(nsIFile* path) { + nsresult rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { + NS_WARNING("Could not create root directory for message folder"); + return rv; + } + + // Create tmp, cur leaves + nsCOMPtr<nsIFile> leaf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + leaf->InitWithFile(path); + + leaf->AppendNative("tmp"_ns); + rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { + NS_WARNING("Could not create tmp directory for message folder"); + return rv; + } + + leaf->SetNativeLeafName("cur"_ns); + rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { + NS_WARNING("Could not create cur directory for message folder"); + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::CreateFolder(nsIMsgFolder* aParent, + const nsAString& aFolderName, + nsIMsgFolder** aResult) { + NS_ENSURE_ARG_POINTER(aParent); + NS_ENSURE_ARG_POINTER(aResult); + if (aFolderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME; + + nsCOMPtr<nsIFile> path; + nsresult rv = aParent->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get a directory based on our current path + bool isServer; + aParent->GetIsServer(&isServer); + rv = CreateDirectoryForFolder(path, isServer); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure the new folder name is valid + nsAutoString safeFolderName(aFolderName); + NS_MsgHashIfNecessary(safeFolderName); + + path->Append(safeFolderName); + bool exists; + path->Exists(&exists); + if (exists) // check this because localized names are different from disk + // names + return NS_MSG_FOLDER_EXISTS; + + rv = CreateMaildir(path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> child; + // GetFlags and SetFlags in AddSubfolder will fail because we have no db at + // this point but mFlags is set. + rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child)); + if (!child || NS_FAILED(rv)) { + path->Remove(true); // recursive + return rv; + } + + // Create an empty database for this mail folder, set its name from the user + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + if (msgDBService) { + nsCOMPtr<nsIMsgDatabase> unusedDB; + rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB)); + + if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) && + unusedDB) { + // need to set the folder name + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (NS_SUCCEEDED(rv)) folderInfo->SetMailboxName(safeFolderName); + + unusedDB->SetSummaryValid(true); + unusedDB->Close(true); + aParent->UpdateSummaryTotals(true); + } else { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("CreateFolder - failed creating db for new folder")); + path->Remove(true); // recursive + rv = NS_MSG_CANT_CREATE_FOLDER; + } + } + child.forget(aResult); + return rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::HasSpaceAvailable(nsIMsgFolder* aFolder, + int64_t aSpaceRequested, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aFolder); + + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested); + if (!*aResult) return NS_ERROR_FILE_NO_DEVICE_SPACE; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::IsSummaryFileValid(nsIMsgFolder* aFolder, + nsIMsgDatabase* aDB, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aDB); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + nsresult rv = + dbFolderInfo->GetBooleanProperty("maildirValid", false, aResult); + if (!*aResult) { + nsCOMPtr<nsIFile> newFile; + rv = aFolder->GetFilePath(getter_AddRefs(newFile)); + NS_ENSURE_SUCCESS(rv, rv); + newFile->Append(u"cur"_ns); + + // If the "cur" sub-dir doesn't exist, and there are no messages + // in the db, then the folder is probably new and the db is valid. + bool exists; + newFile->Exists(&exists); + if (!exists) { + int32_t numMessages; + dbFolderInfo->GetNumMessages(&numMessages); + if (!numMessages) *aResult = true; + } + } + return rv; +} + +NS_IMETHODIMP nsMsgMaildirStore::SetSummaryFileValid(nsIMsgFolder* aFolder, + nsIMsgDatabase* aDB, + bool aValid) { + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aDB); + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + NS_ENSURE_STATE(dbFolderInfo); + return dbFolderInfo->SetBooleanProperty("maildirValid", aValid); +} + +NS_IMETHODIMP nsMsgMaildirStore::DeleteFolder(nsIMsgFolder* aFolder) { + NS_ENSURE_ARG_POINTER(aFolder); + bool exists; + + // Delete the Maildir itself. + nsCOMPtr<nsIFile> pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + exists = false; + pathFile->Exists(&exists); + if (exists) { + rv = pathFile->Remove(true); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Delete any subfolders (.sbd-suffixed directories). + AddDirectorySeparator(pathFile); + exists = false; + pathFile->Exists(&exists); + if (exists) { + rv = pathFile->Remove(true); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::RenameFolder(nsIMsgFolder* aFolder, + const nsAString& aNewName, + nsIMsgFolder** aNewFolder) { + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aNewFolder); + + // old path + nsCOMPtr<nsIFile> oldPathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // old sbd directory + nsCOMPtr<nsIFile> sbdPathFile; + uint32_t numChildren; + aFolder->GetNumSubFolders(&numChildren); + if (numChildren > 0) { + sbdPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sbdPathFile->InitWithFile(oldPathFile); + NS_ENSURE_SUCCESS(rv, rv); + GetDirectoryForFolder(sbdPathFile); + } + + // old summary + nsCOMPtr<nsIFile> oldSummaryFile; + rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // Validate new name + nsAutoString safeName(aNewName); + NS_MsgHashIfNecessary(safeName); + + aFolder->ForceDBClosed(); + + // rename folder + rv = oldPathFile->MoveTo(nullptr, safeName); + NS_ENSURE_SUCCESS(rv, rv); + + if (numChildren > 0) { + // rename "*.sbd" directory + nsAutoString sbdName = safeName; + sbdName.AppendLiteral(FOLDER_SUFFIX); + sbdPathFile->MoveTo(nullptr, sbdName); + } + + // rename summary + nsAutoString summaryName(safeName); + summaryName.AppendLiteral(SUMMARY_SUFFIX); + oldSummaryFile->MoveTo(nullptr, summaryName); + + nsCOMPtr<nsIMsgFolder> parentFolder; + rv = aFolder->GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) return NS_ERROR_NULL_POINTER; + + return parentFolder->AddSubfolder(safeName, aNewFolder); +} + +NS_IMETHODIMP nsMsgMaildirStore::CopyFolder( + nsIMsgFolder* aSrcFolder, nsIMsgFolder* aDstFolder, bool aIsMoveFolder, + nsIMsgWindow* aMsgWindow, nsIMsgCopyServiceListener* aListener, + const nsAString& aNewName) { + NS_ENSURE_ARG_POINTER(aSrcFolder); + NS_ENSURE_ARG_POINTER(aDstFolder); + + nsAutoString folderName; + if (aNewName.IsEmpty()) + aSrcFolder->GetName(folderName); + else + folderName.Assign(aNewName); + + nsAutoString safeFolderName(folderName); + NS_MsgHashIfNecessary(safeFolderName); + aSrcFolder->ForceDBClosed(); + + nsCOMPtr<nsIFile> oldPath; + nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> summaryFile; + GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile)); + + nsCOMPtr<nsIFile> newPath; + rv = aDstFolder->GetFilePath(getter_AddRefs(newPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // create target directory based on our current path + bool isServer; + aDstFolder->GetIsServer(&isServer); + rv = CreateDirectoryForFolder(newPath, isServer); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> origPath; + oldPath->Clone(getter_AddRefs(origPath)); + + rv = oldPath->CopyTo(newPath, safeFolderName); + NS_ENSURE_SUCCESS(rv, rv); // will fail if a file by that name exists + + // Copy to dir can fail if file does not exist. If copy fails, we test + // if the file exists or not, if it does not that's ok, we continue + // without copying it. If it fails and file exist and is not zero sized + // there is real problem. + nsAutoString dbName(safeFolderName); + dbName.AppendLiteral(SUMMARY_SUFFIX); + rv = summaryFile->CopyTo(newPath, dbName); + if (!NS_SUCCEEDED(rv)) { + // Test if the file is not empty + bool exists; + int64_t fileSize; + summaryFile->Exists(&exists); + summaryFile->GetFileSize(&fileSize); + if (exists && fileSize > 0) + NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked! + // else case is file is zero sized, no need to copy it, + // not an error + // else case is file does not exist - not an error + } + + nsCOMPtr<nsIMsgFolder> newMsgFolder; + rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + newMsgFolder->SetPrettyName(folderName); + uint32_t flags; + aSrcFolder->GetFlags(&flags); + newMsgFolder->SetFlags(flags); + bool changed = false; + rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed); + if (changed) aSrcFolder->AlertFilterChanged(aMsgWindow); + + nsTArray<RefPtr<nsIMsgFolder>> subFolders; + rv = aSrcFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy subfolders to the new location + nsresult copyStatus = NS_OK; + nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder( + do_QueryInterface(newMsgFolder, &rv)); + if (NS_SUCCEEDED(rv)) { + for (nsIMsgFolder* folder : subFolders) { + copyStatus = + localNewFolder->CopyFolderLocal(folder, false, aMsgWindow, aListener); + // Test if the call succeeded, if not we have to stop recursive call + if (NS_FAILED(copyStatus)) { + // Copy failed we have to notify caller to handle the error and stop + // moving the folders. In case this happens to the topmost level of + // recursive call, then we just need to break from the while loop and + // go to error handling code. + if (!aIsMoveFolder) return copyStatus; + break; + } + } + } + + if (aIsMoveFolder && NS_SUCCEEDED(copyStatus)) { + if (localNewFolder) { + nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder)); + localNewFolder->OnCopyCompleted(srcSupport, true); + } + + // Notify that the folder that was dragged and dropped has been created. + // No need to do this for its subfolders - isMoveFolder will be true for + // folder. + aDstFolder->NotifyFolderAdded(newMsgFolder); + + nsCOMPtr<nsIMsgFolder> msgParent; + aSrcFolder->GetParent(getter_AddRefs(msgParent)); + aSrcFolder->SetParent(nullptr); + if (msgParent) { + // The files have already been moved, so delete storage false + msgParent->PropagateDelete(aSrcFolder, false); + oldPath->Remove(true); + aSrcFolder->DeleteStorage(); + + nsCOMPtr<nsIFile> parentPath; + rv = msgParent->GetFilePath(getter_AddRefs(parentPath)); + NS_ENSURE_SUCCESS(rv, rv); + + AddDirectorySeparator(parentPath); + nsCOMPtr<nsIDirectoryEnumerator> children; + parentPath->GetDirectoryEntries(getter_AddRefs(children)); + bool more; + // checks if the directory is empty or not + if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more) + parentPath->Remove(true); + } + } else { + // This is the case where the copy of a subfolder failed. + // We have to delete the newDirectory tree to make a "rollback". + // Someone should add a popup to warn the user that the move was not + // possible. + if (aIsMoveFolder && NS_FAILED(copyStatus)) { + nsCOMPtr<nsIMsgFolder> msgParent; + newMsgFolder->ForceDBClosed(); + newMsgFolder->GetParent(getter_AddRefs(msgParent)); + newMsgFolder->SetParent(nullptr); + if (msgParent) { + msgParent->PropagateDelete(newMsgFolder, false); + newMsgFolder->DeleteStorage(); + AddDirectorySeparator(newPath); + newPath->Remove(true); // berkeley mailbox + } + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::GetNewMsgOutputStream(nsIMsgFolder* aFolder, + nsIMsgDBHdr** aNewMsgHdr, + nsIOutputStream** aResult) { + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aNewMsgHdr); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!*aNewMsgHdr) { + rv = db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr); + NS_ENSURE_SUCCESS(rv, rv); + } + // With maildir, messages have whole file to themselves. + (*aNewMsgHdr)->SetMessageOffset(0); + + // We're going to save the new message into the maildir 'tmp' folder. + // When the message is completed, it can be moved to 'cur'. + nsCOMPtr<nsIFile> newFile; + rv = aFolder->GetFilePath(getter_AddRefs(newFile)); + NS_ENSURE_SUCCESS(rv, rv); + newFile->Append(u"tmp"_ns); + + // let's check if the folder exists + // XXX TODO: kill this and make sure maildir creation includes cur/tmp + bool exists; + newFile->Exists(&exists); + if (!exists) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetNewMsgOutputStream - tmp subfolder does not exist!!")); + rv = newFile->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Generate the 'tmp' file name based on timestamp. + // (We'll use the Message-ID as the basis for the final filename, + // but we don't have headers at this point). + nsAutoCString newName; + newName.AppendInt(static_cast<int64_t>(PR_Now())); + newFile->AppendNative(newName); + + // CreateUnique, in case we get more than one message per millisecond :-) + rv = newFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + newFile->GetNativeLeafName(newName); + // save the file name in the message header - otherwise no way to retrieve it + (*aNewMsgHdr)->SetStringProperty("storeToken", newName); + return MsgNewBufferedFileOutputStream(aResult, newFile, + PR_WRONLY | PR_CREATE_FILE, 00600); +} + +NS_IMETHODIMP +nsMsgMaildirStore::DiscardNewMessage(nsIOutputStream* aOutputStream, + nsIMsgDBHdr* aNewHdr) { + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_ENSURE_ARG_POINTER(aNewHdr); + + aOutputStream->Close(); + // file path is stored in message header property "storeToken" + nsAutoCString fileName; + aNewHdr->GetStringProperty("storeToken", fileName); + if (fileName.IsEmpty()) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> path; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + // path to the message download folder + path->Append(u"tmp"_ns); + path->AppendNative(fileName); + + return path->Remove(false); +} + +NS_IMETHODIMP +nsMsgMaildirStore::FinishNewMessage(nsIOutputStream* aOutputStream, + nsIMsgDBHdr* aNewHdr) { + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_ENSURE_ARG_POINTER(aNewHdr); + + aOutputStream->Close(); + + nsCOMPtr<nsIFile> folderPath; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // tmp filename is stored in "storeToken". + // By now we'll have the Message-ID, which we'll base the final filename on. + nsAutoCString tmpName; + aNewHdr->GetStringProperty("storeToken", tmpName); + if (tmpName.IsEmpty()) { + NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!"); + return NS_ERROR_FAILURE; + } + + // path to the new destination + nsCOMPtr<nsIFile> curPath; + folderPath->Clone(getter_AddRefs(curPath)); + curPath->Append(u"cur"_ns); + + // let's check if the folder exists + // XXX TODO: kill this and make sure maildir creation includes cur/tmp + bool exists; + curPath->Exists(&exists); + if (!exists) { + rv = curPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + // path to the downloaded message + nsCOMPtr<nsIFile> fromPath; + folderPath->Clone(getter_AddRefs(fromPath)); + fromPath->Append(u"tmp"_ns); + fromPath->AppendNative(tmpName); + + // Check that the message is still in tmp. + // XXX TODO: revisit this. I think it's needed because the + // pairing rules for: + // GetNewMsgOutputStream(), FinishNewMessage(), + // MoveNewlyDownloadedMessage() and DiscardNewMessage() + // are not well defined. + // If they are sorted out, this code can be removed. + fromPath->Exists(&exists); + if (!exists) { + // Perhaps the message has already moved. See bug 1028372 to fix this. + nsCOMPtr<nsIFile> existingPath; + curPath->Clone(getter_AddRefs(existingPath)); + existingPath->AppendNative(tmpName); + existingPath->Exists(&exists); + if (exists) // then there is nothing to do + return NS_OK; + + NS_ERROR("FinishNewMessage - oops! file does not exist!"); + return NS_ERROR_FILE_NOT_FOUND; + } + + nsCString msgID; + aNewHdr->GetMessageId(getter_Copies(msgID)); + + nsCString baseName; + // For missing or suspiciously-short Message-IDs, use a timestamp + // instead. + // This also avoids some special filenames we can't use in windows (CON, + // AUX, NUL, LPT1 etc...). With an extension (eg "LPT4.txt") they're all + // below 9 chars. + if (msgID.Length() < 9) { + baseName.AppendInt(static_cast<int64_t>(PR_Now())); + } else { + percentEncode(msgID, baseName); + // No length limit on Message-Id header, but lets clip our filenames + // well below any MAX_PATH limits. + if (baseName.Length() > (128 - 4)) { + baseName.SetLength(128 - 4); // (4 for ".eml") + } + } + + nsCOMPtr<nsIFile> toPath; + curPath->Clone(getter_AddRefs(toPath)); + nsCString toName(baseName); + toName.Append(".eml"); + toPath->AppendNative(toName); + + // Using CreateUnique in case we have duplicate Message-Ids + rv = toPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) { + // NS_ERROR_FILE_TOO_BIG means CreateUnique() bailed out at 10000 attempts. + if (rv != NS_ERROR_FILE_TOO_BIG) { + NS_ENSURE_SUCCESS(rv, rv); + } + // As a last resort, fall back to using timestamp as filename. + toName.SetLength(0); + toName.AppendInt(static_cast<int64_t>(PR_Now())); + toName.Append(".eml"); + toPath->SetNativeLeafName(toName); + rv = toPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Move into place (using whatever name CreateUnique() settled upon). + toPath->GetNativeLeafName(toName); + rv = fromPath->MoveToNative(curPath, toName); + NS_ENSURE_SUCCESS(rv, rv); + // Update the db to reflect the final filename. + aNewHdr->SetStringProperty("storeToken", toName); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr* aHdr, + nsIMsgFolder* aDestFolder, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aHdr); + NS_ENSURE_ARG_POINTER(aDestFolder); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIFile> folderPath; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // file path is stored in message header property + nsAutoCString fileName; + aHdr->GetStringProperty("storeToken", fileName); + if (fileName.IsEmpty()) { + NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!"); + return NS_ERROR_FAILURE; + } + + // path to the downloaded message + nsCOMPtr<nsIFile> fromPath; + folderPath->Clone(getter_AddRefs(fromPath)); + fromPath->Append(u"cur"_ns); + fromPath->AppendNative(fileName); + + // let's check if the tmp file exists + bool exists; + fromPath->Exists(&exists); + if (!exists) { + NS_ERROR("FinishNewMessage - oops! file does not exist!"); + return NS_ERROR_FAILURE; + } + + // move to the "cur" subfolder + nsCOMPtr<nsIFile> toPath; + aDestFolder->GetFilePath(getter_AddRefs(folderPath)); + folderPath->Clone(getter_AddRefs(toPath)); + toPath->Append(u"cur"_ns); + + // let's check if the folder exists + // XXX TODO: kill this and make sure maildir creation includes cur/tmp + toPath->Exists(&exists); + if (!exists) { + rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIMsgDatabase> destMailDB; + rv = aDestFolder->GetMsgDatabase(getter_AddRefs(destMailDB)); + NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv), + "failed to open mail db moving message"); + + nsCOMPtr<nsIMsgDBHdr> newHdr; + if (destMailDB) + rv = destMailDB->CopyHdrFromExistingHdr(nsMsgKey_None, aHdr, true, + getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && !newHdr) rv = NS_ERROR_UNEXPECTED; + + if (NS_FAILED(rv)) { + aDestFolder->ThrowAlertMsg("filterFolderHdrAddFailed", nullptr); + return rv; + } + + nsCOMPtr<nsIFile> existingPath; + toPath->Clone(getter_AddRefs(existingPath)); + existingPath->AppendNative(fileName); + existingPath->Exists(&exists); + + if (exists) { + rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + existingPath->GetNativeLeafName(fileName); + newHdr->SetStringProperty("storeToken", fileName); + } + + rv = fromPath->MoveToNative(toPath, fileName); + *aResult = NS_SUCCEEDED(rv); + if (NS_FAILED(rv)) + aDestFolder->ThrowAlertMsg("filterFolderWriteFailed", nullptr); + + if (NS_FAILED(rv)) { + if (destMailDB) destMailDB->Close(true); + + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + bool movedMsgIsNew = false; + // if we have made it this far then the message has successfully been + // written to the new folder now add the header to the destMailDB. + + uint32_t newFlags; + newHdr->GetFlags(&newFlags); + nsMsgKey msgKey; + newHdr->GetMessageKey(&msgKey); + if (!(newFlags & nsMsgMessageFlags::Read)) { + nsCString junkScoreStr; + (void)newHdr->GetStringProperty("junkscore", junkScoreStr); + if (atoi(junkScoreStr.get()) != nsIJunkMailPlugin::IS_SPAM_SCORE) { + newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + destMailDB->AddToNewList(msgKey); + movedMsgIsNew = true; + } + } + + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) notifier->NotifyMsgAdded(newHdr); + + if (movedMsgIsNew) { + aDestFolder->SetHasNewMessages(true); + + // Notify the message was moved. + if (notifier) { + notifier->NotifyMsgUnincorporatedMoved(folder, newHdr); + } + } + + nsCOMPtr<nsIMsgDatabase> sourceDB; + rv = folder->GetMsgDatabase(getter_AddRefs(sourceDB)); + + if (NS_SUCCEEDED(rv) && sourceDB) sourceDB->RemoveHeaderMdbRow(aHdr); + + destMailDB->SetSummaryValid(true); + aDestFolder->UpdateSummaryTotals(true); + destMailDB->Commit(nsMsgDBCommitType::kLargeCommit); + return rv; +} + +NS_IMETHODIMP +nsMsgMaildirStore::GetMsgInputStream(nsIMsgFolder* aMsgFolder, + const nsACString& aMsgToken, + nsIInputStream** aResult) { + NS_ENSURE_ARG_POINTER(aMsgFolder); + NS_ENSURE_ARG_POINTER(aResult); + + // construct path to file + nsCOMPtr<nsIFile> path; + nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aMsgToken.IsEmpty()) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetMsgInputStream - empty storeToken!!")); + return NS_ERROR_FAILURE; + } + + path->Append(u"cur"_ns); + + // let's check if the folder exists + // XXX TODO: kill this and make sure maildir creation includes cur/tmp + bool exists; + path->Exists(&exists); + if (!exists) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetMsgInputStream - oops! cur subfolder does not exist!")); + rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + path->AppendNative(aMsgToken); + return NS_NewLocalFileInputStream(aResult, path); +} + +NS_IMETHODIMP nsMsgMaildirStore::DeleteMessages( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray) { + nsCOMPtr<nsIMsgFolder> folder; + + for (auto msgHdr : aHdrArray) { + msgHdr->GetFolder(getter_AddRefs(folder)); + nsCOMPtr<nsIFile> path; + nsresult rv = folder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString fileName; + msgHdr->GetStringProperty("storeToken", fileName); + + if (fileName.IsEmpty()) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("DeleteMessages - empty storeToken!!")); + // Perhaps an offline store has not downloaded this particular message. + continue; + } + + path->Append(u"cur"_ns); + path->AppendNative(fileName); + + // Let's check if the message exists. + bool exists; + path->Exists(&exists); + if (!exists) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("DeleteMessages - file does not exist !!")); + // Perhaps an offline store has not downloaded this particular message. + continue; + } + path->Remove(false); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::CopyMessages(bool aIsMove, + const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, + nsIMsgFolder* aDstFolder, + nsTArray<RefPtr<nsIMsgDBHdr>>& aDstHdrs, + nsITransaction** aUndoAction, bool* aCopyDone) { + NS_ENSURE_ARG_POINTER(aDstFolder); + NS_ENSURE_ARG_POINTER(aCopyDone); + NS_ENSURE_ARG_POINTER(aUndoAction); + + *aCopyDone = false; + if (aHdrArray.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIMsgFolder> srcFolder; + nsresult rv; + nsIMsgDBHdr* msgHdr = aHdrArray[0]; + rv = msgHdr->GetFolder(getter_AddRefs(srcFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + // Both source and destination folders must use maildir type store. + nsCOMPtr<nsIMsgPluggableStore> srcStore; + nsAutoCString srcType; + srcFolder->GetMsgStore(getter_AddRefs(srcStore)); + if (srcStore) srcStore->GetStoreType(srcType); + nsCOMPtr<nsIMsgPluggableStore> dstStore; + nsAutoCString dstType; + aDstFolder->GetMsgStore(getter_AddRefs(dstStore)); + if (dstStore) dstStore->GetStoreType(dstType); + if (!srcType.EqualsLiteral("maildir") || !dstType.EqualsLiteral("maildir")) + return NS_OK; + + // Both source and destination must be local folders. In theory we could + // do efficient copies of the offline store of IMAP, but this is not + // supported yet. For that, we need to deal with both correct handling + // of deletes from the src server, and msgKey = UIDL in the dst folder. + nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder( + do_QueryInterface(aDstFolder)); + if (!destLocalFolder) return NS_OK; + nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder(do_QueryInterface(srcFolder)); + if (!srcLocalFolder) return NS_OK; + + // We should be able to use a file move for an efficient copy. + + nsCOMPtr<nsIFile> destFolderPath; + nsCOMPtr<nsIMsgDatabase> destDB; + aDstFolder->GetMsgDatabase(getter_AddRefs(destDB)); + rv = aDstFolder->GetFilePath(getter_AddRefs(destFolderPath)); + NS_ENSURE_SUCCESS(rv, rv); + destFolderPath->Append(u"cur"_ns); + + nsCOMPtr<nsIFile> srcFolderPath; + rv = srcFolder->GetFilePath(getter_AddRefs(srcFolderPath)); + NS_ENSURE_SUCCESS(rv, rv); + srcFolderPath->Append(u"cur"_ns); + + nsCOMPtr<nsIMsgDatabase> srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn; + NS_ENSURE_TRUE(msgTxn, NS_ERROR_OUT_OF_MEMORY); + if (NS_SUCCEEDED(msgTxn->Init(srcFolder, aDstFolder, aIsMove))) { + if (aIsMove) + msgTxn->SetTransactionType(nsIMessenger::eMoveMsg); + else + msgTxn->SetTransactionType(nsIMessenger::eCopyMsg); + } + + aDstHdrs.Clear(); + aDstHdrs.SetCapacity(aHdrArray.Length()); + + for (auto srcHdr : aHdrArray) { + nsMsgKey srcKey; + srcHdr->GetMessageKey(&srcKey); + msgTxn->AddSrcKey(srcKey); + nsAutoCString fileName; + srcHdr->GetStringProperty("storeToken", fileName); + if (fileName.IsEmpty()) { + MOZ_LOG(MailDirLog, mozilla::LogLevel::Info, + ("GetMsgInputStream - empty storeToken!!")); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> srcFile; + rv = srcFolderPath->Clone(getter_AddRefs(srcFile)); + NS_ENSURE_SUCCESS(rv, rv); + srcFile->AppendNative(fileName); + + nsCOMPtr<nsIFile> destFile; + destFolderPath->Clone(getter_AddRefs(destFile)); + destFile->AppendNative(fileName); + bool exists; + destFile->Exists(&exists); + if (exists) { + rv = destFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + destFile->GetNativeLeafName(fileName); + } + if (aIsMove) + rv = srcFile->MoveToNative(destFolderPath, fileName); + else + rv = srcFile->CopyToNative(destFolderPath, fileName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgDBHdr> destHdr; + if (destDB) { + rv = destDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, true, + getter_AddRefs(destHdr)); + NS_ENSURE_SUCCESS(rv, rv); + destHdr->SetStringProperty("storeToken", fileName); + aDstHdrs.AppendElement(destHdr); + nsMsgKey dstKey; + destHdr->GetMessageKey(&dstKey); + msgTxn->AddDstKey(dstKey); + } + } + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) { + notifier->NotifyMsgsMoveCopyCompleted(aIsMove, aHdrArray, aDstFolder, + aDstHdrs); + } + + // For now, we only support local dest folders, and for those we are done and + // can delete the messages. Perhaps this should be moved into the folder + // when we try to support other folder types. + if (aIsMove) { + for (auto msgDBHdr : aHdrArray) { + srcDB->DeleteHeader(msgDBHdr, nullptr, false, true); + } + } + + *aCopyDone = true; + msgTxn.forget(aUndoAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMaildirStore::GetSupportsCompaction(bool* aSupportsCompaction) { + NS_ENSURE_ARG_POINTER(aSupportsCompaction); + *aSupportsCompaction = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::CompactFolder(nsIMsgFolder* aFolder, + nsIUrlListener* aListener, + nsIMsgWindow* aMsgWindow) { + return NS_OK; +} + +class MaildirStoreParser { + public: + MaildirStoreParser(nsIMsgFolder* aFolder, nsIMsgDatabase* aMsgDB, + nsIDirectoryEnumerator* aDirectoryEnumerator, + nsIUrlListener* aUrlListener); + virtual ~MaildirStoreParser(); + + nsresult ParseNextMessage(nsIFile* aFile); + static void TimerCallback(nsITimer* aTimer, void* aClosure); + nsresult StartTimer(); + + nsCOMPtr<nsIDirectoryEnumerator> m_directoryEnumerator; + nsCOMPtr<nsIMsgFolder> m_folder; + nsCOMPtr<nsIMsgDatabase> m_db; + nsCOMPtr<nsITimer> m_timer; + nsCOMPtr<nsIUrlListener> m_listener; +}; + +MaildirStoreParser::MaildirStoreParser(nsIMsgFolder* aFolder, + nsIMsgDatabase* aMsgDB, + nsIDirectoryEnumerator* aDirEnum, + nsIUrlListener* aUrlListener) { + m_folder = aFolder; + m_db = aMsgDB; + m_directoryEnumerator = aDirEnum; + m_listener = aUrlListener; +} + +MaildirStoreParser::~MaildirStoreParser() {} + +nsresult MaildirStoreParser::ParseNextMessage(nsIFile* aFile) { + nsresult rv; + NS_ENSURE_TRUE(m_db, NS_ERROR_NULL_POINTER); + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIMsgParseMailMsgState> msgParser = + do_CreateInstance("@mozilla.org/messenger/messagestateparser;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgParser->SetMailDB(m_db); + nsCOMPtr<nsIMsgDBHdr> newMsgHdr; + rv = m_db->CreateNewHdr(nsMsgKey_None, getter_AddRefs(newMsgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + newMsgHdr->SetMessageOffset(0); + + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + if (NS_SUCCEEDED(rv) && inputStream) { + RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer = + new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false); + int64_t fileSize; + aFile->GetFileSize(&fileSize); + msgParser->SetNewMsgHdr(newMsgHdr); + msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState); + bool needMoreData = false; + char* newLine = nullptr; + uint32_t numBytesInLine = 0; + // we only have to read the headers, because we know the message size + // from the file size. So we can do this in one time slice. + do { + newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine, + needMoreData); + if (newLine) { + msgParser->ParseAFolderLine(newLine, numBytesInLine); + free(newLine); + } + } while (newLine && numBytesInLine > 0); + + msgParser->FinishHeader(); + // A single message needs to be less than 4GB + newMsgHdr->SetMessageSize((uint32_t)fileSize); + m_db->AddNewHdrToDB(newMsgHdr, true); + nsAutoCString storeToken; + aFile->GetNativeLeafName(storeToken); + newMsgHdr->SetStringProperty("storeToken", storeToken); + } + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +void MaildirStoreParser::TimerCallback(nsITimer* aTimer, void* aClosure) { + MaildirStoreParser* parser = (MaildirStoreParser*)aClosure; + bool hasMore; + parser->m_directoryEnumerator->HasMoreElements(&hasMore); + if (!hasMore) { + nsCOMPtr<nsIMsgPluggableStore> store; + parser->m_folder->GetMsgStore(getter_AddRefs(store)); + parser->m_timer->Cancel(); + if (parser->m_db) parser->m_db->SetSummaryValid(true); + // store->SetSummaryFileValid(parser->m_folder, parser->m_db, true); + if (parser->m_listener) { + nsresult rv; + nsCOMPtr<nsIMailboxUrl> mailboxurl = + do_CreateInstance("@mozilla.org/messenger/mailboxurl;1", &rv); + if (NS_SUCCEEDED(rv) && mailboxurl) { + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl); + url->SetUpdatingFolder(true); + nsAutoCString uriSpec("mailbox://"); + // ### TODO - what if SetSpec fails? + (void)url->SetSpecInternal(uriSpec); + parser->m_listener->OnStopRunningUrl(url, NS_OK); + } + } + // Parsing complete and timer cancelled, so we release the parser object. + delete parser; + return; + } + nsCOMPtr<nsIFile> currentFile; + nsresult rv = + parser->m_directoryEnumerator->GetNextFile(getter_AddRefs(currentFile)); + if (NS_SUCCEEDED(rv)) rv = parser->ParseNextMessage(currentFile); + if (NS_FAILED(rv) && parser->m_listener) + parser->m_listener->OnStopRunningUrl(nullptr, NS_ERROR_FAILURE); +} + +nsresult MaildirStoreParser::StartTimer() { + nsresult rv; + m_timer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_timer->InitWithNamedFuncCallback(TimerCallback, (void*)this, 0, + nsITimer::TYPE_REPEATING_SLACK, + "MaildirStoreParser::TimerCallback"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::RebuildIndex(nsIMsgFolder* aFolder, + nsIMsgDatabase* aMsgDB, + nsIMsgWindow* aMsgWindow, + nsIUrlListener* aListener) { + NS_ENSURE_ARG_POINTER(aFolder); + // This code needs to iterate over the maildir files, and parse each + // file and add a msg hdr to the db for the file. + nsCOMPtr<nsIFile> path; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + path->Append(u"cur"_ns); + + nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator; + rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + MaildirStoreParser* fileParser = + new MaildirStoreParser(aFolder, aMsgDB, directoryEnumerator, aListener); + NS_ENSURE_TRUE(fileParser, NS_ERROR_OUT_OF_MEMORY); + fileParser->StartTimer(); + ResetForceReparse(aMsgDB); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::ChangeFlags( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, uint32_t aFlags, + bool aSet) { + for (auto msgHdr : aHdrArray) { + // get output stream for header + nsCOMPtr<nsIOutputStream> outputStream; + nsresult rv = GetOutputStream(msgHdr, outputStream); + NS_ENSURE_SUCCESS(rv, rv); + + // Work out the flags we want to write. + uint32_t flags = 0; + (void)msgHdr->GetFlags(&flags); + flags &= ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline); + if (aSet) { + flags |= aFlags; + } else { + flags &= ~aFlags; + } + + // Rewrite X-Mozilla-Status headers. + nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(outputStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = RewriteMsgFlags(seekable, flags); + if (NS_FAILED(rv)) NS_WARNING("updateFolderFlag failed"); + } + return NS_OK; +} + +// get output stream from header +nsresult nsMsgMaildirStore::GetOutputStream( + nsIMsgDBHdr* aHdr, nsCOMPtr<nsIOutputStream>& aOutputStream) { + // file name is stored in message header property "storeToken" + nsAutoCString fileName; + aHdr->GetStringProperty("storeToken", fileName); + if (fileName.IsEmpty()) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = aHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> folderPath; + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> maildirFile; + folderPath->Clone(getter_AddRefs(maildirFile)); + maildirFile->Append(u"cur"_ns); + maildirFile->AppendNative(fileName); + + return MsgGetFileStream(maildirFile, getter_AddRefs(aOutputStream)); +} + +NS_IMETHODIMP nsMsgMaildirStore::ChangeKeywords( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, const nsACString& aKeywords, + bool aAdd) { + if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG; + + nsTArray<nsCString> keywordsToAdd; + nsTArray<nsCString> keywordsToRemove; + if (aAdd) { + ParseString(aKeywords, ' ', keywordsToAdd); + } else { + ParseString(aKeywords, ' ', keywordsToRemove); + } + + for (auto msgHdr : aHdrArray) { + // Open the message file. + nsCOMPtr<nsIOutputStream> output; + nsresult rv = GetOutputStream(msgHdr, output); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(output, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool notEnoughRoom; + rv = ChangeKeywordsHelper(seekable, keywordsToAdd, keywordsToRemove, + notEnoughRoom); + NS_ENSURE_SUCCESS(rv, rv); + if (notEnoughRoom) { + // The growKeywords property indicates that the X-Mozilla-Keys header + // doesn't have enough space, and should be rebuilt during the next + // folder compaction. + // TODO: For maildir there is no compaction, so this'll have no effect! + msgHdr->SetUint32Property("growKeywords", 1); + } + output->Close(); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgMaildirStore::GetStoreType(nsACString& aType) { + aType.AssignLiteral("maildir"); + return NS_OK; +} + +/** + * Finds the directory associated with this folder. That is if the path is + * c:\Inbox, it will return c:\Inbox.sbd if it succeeds. Path is strictly + * an out parameter. + */ +nsresult nsMsgMaildirStore::GetDirectoryForFolder(nsIFile* path) { + // add directory separator to the path + nsAutoString leafName; + path->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + return path->SetLeafName(leafName); +} + +nsresult nsMsgMaildirStore::CreateDirectoryForFolder(nsIFile* path, + bool aIsServer) { + nsresult rv = NS_OK; + if (!aIsServer) { + rv = GetDirectoryForFolder(path); + NS_ENSURE_SUCCESS(rv, rv); + } + bool pathIsDirectory = false; + path->IsDirectory(&pathIsDirectory); + if (!pathIsDirectory) { + bool pathExists; + path->Exists(&pathExists); + // If for some reason there's a file with the directory separator + // then we are going to fail. + rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY + : path->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + return rv; +} + +NS_IMETHODIMP +nsMsgMaildirStore::SliceStream(nsIInputStream* inStream, uint64_t start, + uint32_t length, nsIInputStream** result) { + nsCOMPtr<nsIInputStream> in(inStream); + RefPtr<mozilla::SlicedInputStream> slicedStream = + new mozilla::SlicedInputStream(in.forget(), start, uint64_t(length)); + slicedStream.forget(result); + return NS_OK; +} |