diff options
Diffstat (limited to 'mailnews/local/src/nsParseMailbox.cpp')
-rw-r--r-- | mailnews/local/src/nsParseMailbox.cpp | 2624 |
1 files changed, 2624 insertions, 0 deletions
diff --git a/mailnews/local/src/nsParseMailbox.cpp b/mailnews/local/src/nsParseMailbox.cpp new file mode 100644 index 000000000..9d68e5cd1 --- /dev/null +++ b/mailnews/local/src/nsParseMailbox.cpp @@ -0,0 +1,2624 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsIURI.h" +#include "nsParseMailbox.h" +#include "nsIMsgHdr.h" +#include "nsIMsgDatabase.h" +#include "nsMsgMessageFlags.h" +#include "nsIDBFolderInfo.h" +#include "nsIInputStream.h" +#include "nsIFile.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgBaseCID.h" +#include "nsMsgDBCID.h" +#include "nsIMailboxUrl.h" +#include "nsNetUtil.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgFolder.h" +#include "nsIURL.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgFilterList.h" +#include "nsIMsgFilter.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsRDFCID.h" +#include "nsIRDFService.h" +#include "nsMsgI18N.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsMsgUtils.h" +#include "prprf.h" +#include "prmem.h" +#include "nsISeekableStream.h" +#include "nsIMimeHeaders.h" +#include "nsIMsgMdnGenerator.h" +#include "nsMsgSearchCore.h" +#include "nsMailHeaders.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgComposeParams.h" +#include "nsMsgCompCID.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDocShell.h" +#include "nsIMsgCompose.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIMsgComposeService.h" +#include "nsIMsgCopyService.h" +#include "nsICryptoHash.h" +#include "nsIStringBundle.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIMsgFilterCustomAction.h" +#include <ctype.h> +#include "nsIMsgPluggableStore.h" +#include "mozilla/Services.h" +#include "nsQueryObject.h" +#include "nsIOutputStream.h" +#include "mozilla/Attributes.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +/* the following macros actually implement addref, release and query interface for our component. */ +NS_IMPL_ISUPPORTS_INHERITED(nsMsgMailboxParser, + nsParseMailMessageState, + nsIStreamListener, + nsIRequestObserver) + +// Whenever data arrives from the connection, core netlib notifices the protocol by calling +// OnDataAvailable. We then read and process the incoming data from the input stream. +NS_IMETHODIMP nsMsgMailboxParser::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *aIStream, uint64_t sourceOffset, uint32_t aLength) +{ + // right now, this really just means turn around and process the url + nsresult rv = NS_OK; + nsCOMPtr<nsIURI> url = do_QueryInterface(ctxt, &rv); + if (NS_SUCCEEDED(rv)) + rv = ProcessMailboxInputStream(url, aIStream, aLength); + return rv; +} + +NS_IMETHODIMP nsMsgMailboxParser::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + m_startTime = PR_Now(); + + + // extract the appropriate event sinks from the url and initialize them in our protocol data + // the URL should be queried for a nsIMailboxURL. If it doesn't support a mailbox URL interface then + // we have an error. + nsresult rv = NS_OK; + + nsCOMPtr<nsIIOService> ioServ = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioServ, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIMailboxUrl> runningUrl = do_QueryInterface(ctxt, &rv); + + nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(ctxt); + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + + if (NS_SUCCEEDED(rv) && runningUrl && folder) + { + url->GetStatusFeedback(getter_AddRefs(m_statusFeedback)); + + // okay, now fill in our event sinks...Note that each getter ref counts before + // it returns the interface to us...we'll release when we are done + + folder->GetName(m_folderName); + + nsCOMPtr<nsIFile> path; + folder->GetFilePath(getter_AddRefs(path)); + + if (path) + { + int64_t fileSize; + path->GetFileSize(&fileSize); + // the size of the mailbox file is our total base line for measuring progress + m_graph_progress_total = fileSize; + UpdateStatusText("buildingSummary"); + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + // Use OpenFolderDB to always open the db so that db's m_folder + // is set correctly. + rv = msgDBService->OpenFolderDB(folder, true, + getter_AddRefs(m_mailDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + rv = msgDBService->CreateNewDB(folder, + getter_AddRefs(m_mailDB)); + + if (m_mailDB) + m_mailDB->AddListener(this); + } + NS_ASSERTION(m_mailDB, "failed to open mail db parsing folder"); + + // try to get a backup message database + nsresult rvignore = folder->GetBackupMsgDatabase( + getter_AddRefs(m_backupMailDB)); + + // We'll accept failures and move on, as we're dealing with some + // sort of unknown problem to begin with. + if (NS_FAILED(rvignore)) + { + if (m_backupMailDB) + m_backupMailDB->RemoveListener(this); + m_backupMailDB = nullptr; + } + else if (m_backupMailDB) + { + m_backupMailDB->AddListener(this); + } + } + } + + // need to get the mailbox name out of the url and call SetMailboxName with it. + // then, we need to open the mail db for this parser. + return rv; +} + +// stop binding is a "notification" informing us that the stream associated with aURL is going away. +NS_IMETHODIMP nsMsgMailboxParser::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) +{ + DoneParsingFolder(aStatus); + // what can we do? we can close the stream? + m_urlInProgress = false; // don't close the connection...we may be re-using it. + + if (m_mailDB) + m_mailDB->RemoveListener(this); + // and we want to mark ourselves for deletion or some how inform our protocol manager that we are + // available for another url if there is one.... + + ReleaseFolderLock(); + // be sure to clear any status text and progress info.. + m_graph_progress_received = 0; + UpdateProgressPercent(); + UpdateStatusText("localStatusDocumentDone"); + + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, + bool aPreChange, uint32_t *aStatus, nsIDBChangeListener * aInstigator) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, + uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, + nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::OnHdrAdded(nsIMsgDBHdr *aHdrAdded, + nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnParentChanged(nsMsgKey aKeyChanged, + nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + if (m_backupMailDB && m_backupMailDB == instigator) + { + m_backupMailDB->RemoveListener(this); + m_backupMailDB = nullptr; + } + else if (m_mailDB) + { + m_mailDB->RemoveListener(this); + m_mailDB = nullptr; + m_newMsgHdr = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::OnEvent(nsIMsgDatabase *aDB, const char *aEvent) +{ + return NS_OK; +} + +/* void OnReadChanged (in nsIDBChangeListener instigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnReadChanged(nsIDBChangeListener *instigator) +{ + return NS_OK; +} + +/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */ +NS_IMETHODIMP +nsParseMailMessageState::OnJunkScoreChanged(nsIDBChangeListener *instigator) +{ + return NS_OK; +} + +nsMsgMailboxParser::nsMsgMailboxParser() : nsMsgLineBuffer(nullptr, false) +{ + Init(); +} + +nsMsgMailboxParser::nsMsgMailboxParser(nsIMsgFolder *aFolder) : nsMsgLineBuffer(nullptr, false) +{ + m_folder = do_GetWeakReference(aFolder); +} + +nsMsgMailboxParser::~nsMsgMailboxParser() +{ + ReleaseFolderLock(); +} + +nsresult nsMsgMailboxParser::Init() +{ + m_obuffer = nullptr; + m_obuffer_size = 0; + m_graph_progress_total = 0; + m_graph_progress_received = 0; + return AcquireFolderLock(); +} + +void nsMsgMailboxParser::UpdateStatusText (const char* stringName) +{ + if (m_statusFeedback) + { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return; + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + return; + nsString finalString; + const char16_t * stringArray[] = { m_folderName.get() }; + rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(stringName).get(), + stringArray, 1, getter_Copies(finalString)); + m_statusFeedback->ShowStatusString(finalString); + } +} + +void nsMsgMailboxParser::UpdateProgressPercent () +{ + if (m_statusFeedback && m_graph_progress_total != 0) + { + // prevent overflow by dividing both by 100 + int64_t progressTotal = m_graph_progress_total / 100; + int64_t progressReceived = m_graph_progress_received / 100; + if (progressTotal > 0) + m_statusFeedback->ShowProgress((100 *(progressReceived)) / progressTotal); + } +} + +nsresult nsMsgMailboxParser::ProcessMailboxInputStream(nsIURI* aURL, nsIInputStream *aIStream, uint32_t aLength) +{ + nsresult ret = NS_OK; + + uint32_t bytesRead = 0; + + if (NS_SUCCEEDED(m_inputStream.GrowBuffer(aLength))) + { + // OK, this sucks, but we're going to have to copy into our + // own byte buffer, and then pass that to the line buffering code, + // which means a couple buffer copies. + ret = aIStream->Read(m_inputStream.GetBuffer(), aLength, &bytesRead); + if (NS_SUCCEEDED(ret)) + ret = BufferInput(m_inputStream.GetBuffer(), bytesRead); + } + if (m_graph_progress_total > 0) + { + if (NS_SUCCEEDED(ret)) + m_graph_progress_received += bytesRead; + } + return (ret); +} + +void nsMsgMailboxParser::DoneParsingFolder(nsresult status) +{ + /* End of file. Flush out any partial line remaining in the buffer. */ + FlushLastLine(); + PublishMsgHeader(nullptr); + + // only mark the db valid if we've succeeded. + if (NS_SUCCEEDED(status) && m_mailDB) // finished parsing, so flush db folder info + UpdateDBFolderInfo(); + else if (m_mailDB) + m_mailDB->SetSummaryValid(false); + + // remove the backup database + if (m_backupMailDB) + { + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + if (folder) + folder->RemoveBackupMsgDatabase(); + m_backupMailDB = nullptr; + } + + // if (m_folder != nullptr) + // m_folder->SummaryChanged(); + FreeBuffers(); +} + +void nsMsgMailboxParser::FreeBuffers() +{ + /* We're done reading the folder - we don't need these things + any more. */ + PR_FREEIF (m_obuffer); + m_obuffer_size = 0; +} + +void nsMsgMailboxParser::UpdateDBFolderInfo() +{ + UpdateDBFolderInfo(m_mailDB); +} + +// update folder info in db so we know not to reparse. +void nsMsgMailboxParser::UpdateDBFolderInfo(nsIMsgDatabase *mailDB) +{ + mailDB->SetSummaryValid(true); +} + +// Tell the world about the message header (add to db, and view, if any) +int32_t nsMsgMailboxParser::PublishMsgHeader(nsIMsgWindow *msgWindow) +{ + FinishHeader(); + if (m_newMsgHdr) + { + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_envelope_pos); + m_newMsgHdr->SetStringProperty("storeToken", storeToken); + + uint32_t flags; + (void)m_newMsgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Expunged) + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + uint32_t size; + (void)m_newMsgHdr->GetMessageSize(&size); + folderInfo->ChangeExpungedBytes(size); + m_newMsgHdr = nullptr; + } + else if (m_mailDB) + { + // add hdr but don't notify - shouldn't be requiring notifications + // during summary file rebuilding + m_mailDB->AddNewHdrToDB(m_newMsgHdr, false); + m_newMsgHdr = nullptr; + } + else + NS_ASSERTION(false, "no database while parsing local folder"); // should have a DB, no? + } + else if (m_mailDB) + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (folderInfo) + folderInfo->ChangeExpungedBytes(m_position - m_envelope_pos); + } + return 0; +} + +void nsMsgMailboxParser::AbortNewHeader() +{ + if (m_newMsgHdr && m_mailDB) + m_newMsgHdr = nullptr; +} + +void nsMsgMailboxParser::OnNewMessage(nsIMsgWindow *msgWindow) +{ + PublishMsgHeader(msgWindow); + Clear(); +} + +nsresult nsMsgMailboxParser::HandleLine(const char *line, uint32_t lineLength) +{ + /* If this is the very first line of a non-empty folder, make sure it's an envelope */ + if (m_graph_progress_received == 0) + { + /* This is the first block from the file. Check to see if this + looks like a mail file. */ + const char *s = line; + const char *end = s + lineLength; + while (s < end && IS_SPACE(*s)) + s++; + if ((end - s) < 20 || !IsEnvelopeLine(s, end - s)) + { +// char buf[500]; +// PR_snprintf (buf, sizeof(buf), +// XP_GetString(MK_MSG_NON_MAIL_FILE_READ_QUESTION), +// folder_name); +// else if (!FE_Confirm (m_context, buf)) +// return NS_MSG_NOT_A_MAIL_FOLDER; /* #### NOT_A_MAIL_FILE */ + } + } +// m_graph_progress_received += lineLength; + + // mailbox parser needs to do special stuff when it finds an envelope + // after parsing a message body. So do that. + if (line[0] == 'F' && IsEnvelopeLine(line, lineLength)) + { + // **** This used to be + // PR_ASSERT (m_parseMsgState->m_state == nsMailboxParseBodyState); + // **** I am not sure this is a right thing to do. This happens when + // going online, downloading a message while playing back append + // draft/template offline operation. We are mixing + // nsMailboxParseBodyState && + // nsMailboxParseHeadersState. David I need your help here too. **** jt + + NS_ASSERTION (m_state == nsIMsgParseMailMsgState::ParseBodyState || + m_state == nsIMsgParseMailMsgState::ParseHeadersState, "invalid parse state"); /* else folder corrupted */ + OnNewMessage(nullptr); + nsresult rv = StartNewEnvelope(line, lineLength); + NS_ASSERTION(NS_SUCCEEDED(rv), " error starting envelope parsing mailbox"); + // at the start of each new message, update the progress bar + UpdateProgressPercent(); + return rv; + } + + // otherwise, the message parser can handle it completely. + if (m_mailDB != nullptr) // if no DB, do we need to parse at all? + return ParseFolderLine(line, lineLength); + + return NS_ERROR_NULL_POINTER; // need to error out if we don't have a db. +} + +void +nsMsgMailboxParser::ReleaseFolderLock() +{ + nsresult result; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + if (!folder) + return; + bool haveSemaphore; + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this)); + result = folder->TestSemaphore(supports, &haveSemaphore); + if (NS_SUCCEEDED(result) && haveSemaphore) + (void) folder->ReleaseSemaphore(supports); +} + +nsresult +nsMsgMailboxParser::AcquireFolderLock() +{ + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder); + if (!folder) + return NS_ERROR_NULL_POINTER; + nsCOMPtr<nsISupports> supports = do_QueryObject(this); + return folder->AcquireSemaphore(supports); +} + +NS_IMPL_ISUPPORTS(nsParseMailMessageState, nsIMsgParseMailMsgState, nsIDBChangeListener) + +nsParseMailMessageState::nsParseMailMessageState() +{ + m_position = 0; + m_new_key = nsMsgKey_None; + m_IgnoreXMozillaStatus = false; + m_state = nsIMsgParseMailMsgState::ParseBodyState; + + // setup handling of custom db headers, headers that are added to .msf files + // as properties of the nsMsgHdr objects, controlled by the + // pref mailnews.customDBHeaders, a space-delimited list of headers. + // E.g., if mailnews.customDBHeaders is "X-Spam-Score", and we're parsing + // a mail message with the X-Spam-Score header, we'll set the + // "x-spam-score" property of nsMsgHdr to the value of the header. + m_customDBHeaderValues = nullptr; + nsCString customDBHeaders; // not shown in search UI + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + { + pPrefBranch->GetCharPref("mailnews.customDBHeaders", getter_Copies(customDBHeaders)); + ToLowerCase(customDBHeaders); + if (customDBHeaders.Find("content-base") == -1) + customDBHeaders.Insert(NS_LITERAL_CSTRING("content-base "), 0); + ParseString(customDBHeaders, ' ', m_customDBHeaders); + + // now add customHeaders + nsCString customHeadersString; // shown in search UI + nsTArray<nsCString> customHeadersArray; + pPrefBranch->GetCharPref("mailnews.customHeaders", getter_Copies(customHeadersString)); + ToLowerCase(customHeadersString); + customHeadersString.StripWhitespace(); + ParseString(customHeadersString, ':', customHeadersArray); + for (uint32_t i = 0; i < customHeadersArray.Length(); i++) + { + if (!m_customDBHeaders.Contains(customHeadersArray[i])) + m_customDBHeaders.AppendElement(customHeadersArray[i]); + } + + if (m_customDBHeaders.Length()) + { + m_customDBHeaderValues = new struct message_header [m_customDBHeaders.Length()]; + if (!m_customDBHeaderValues) + m_customDBHeaders.Clear(); + } + } + Clear(); +} + +nsParseMailMessageState::~nsParseMailMessageState() +{ + ClearAggregateHeader (m_toList); + ClearAggregateHeader (m_ccList); + delete [] m_customDBHeaderValues; +} + +void nsParseMailMessageState::Init(uint64_t fileposition) +{ + m_state = nsIMsgParseMailMsgState::ParseBodyState; + m_position = fileposition; + m_newMsgHdr = nullptr; +} + +NS_IMETHODIMP nsParseMailMessageState::Clear() +{ + m_message_id.length = 0; + m_references.length = 0; + m_date.length = 0; + m_delivery_date.length = 0; + m_from.length = 0; + m_sender.length = 0; + m_newsgroups.length = 0; + m_subject.length = 0; + m_status.length = 0; + m_mozstatus.length = 0; + m_mozstatus2.length = 0; + m_envelope_from.length = 0; + m_envelope_date.length = 0; + m_priority.length = 0; + m_keywords.length = 0; + m_mdn_dnt.length = 0; + m_return_path.length = 0; + m_account_key.length = 0; + m_in_reply_to.length = 0; + m_replyTo.length = 0; + m_content_type.length = 0; + m_mdn_original_recipient.length = 0; + m_bccList.length = 0; + m_body_lines = 0; + m_newMsgHdr = nullptr; + m_envelope_pos = 0; + m_new_key = nsMsgKey_None; + ClearAggregateHeader (m_toList); + ClearAggregateHeader (m_ccList); + m_headers.ResetWritePos(); + m_envelope.ResetWritePos(); + m_receivedTime = 0; + m_receivedValue.Truncate(); + for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) + m_customDBHeaderValues[i].length = 0; + + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetState(nsMailboxParseState aState) +{ + m_state = aState; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::GetState(nsMailboxParseState *aState) +{ + if (!aState) + return NS_ERROR_NULL_POINTER; + + *aState = m_state; + return NS_OK; +} + +NS_IMETHODIMP +nsParseMailMessageState::GetEnvelopePos(uint64_t *aEnvelopePos) +{ + NS_ENSURE_ARG_POINTER(aEnvelopePos); + + *aEnvelopePos = m_envelope_pos; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetEnvelopePos(uint64_t aEnvelopePos) +{ + m_envelope_pos = aEnvelopePos; + m_position = m_envelope_pos; + m_headerstartpos = m_position; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::GetNewMsgHdr(nsIMsgDBHdr ** aMsgHeader) +{ + NS_ENSURE_ARG_POINTER(aMsgHeader); + NS_IF_ADDREF(*aMsgHeader = m_newMsgHdr); + return m_newMsgHdr ? NS_OK : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsParseMailMessageState::SetNewMsgHdr(nsIMsgDBHdr *aMsgHeader) +{ + m_newMsgHdr = aMsgHeader; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::ParseAFolderLine(const char *line, uint32_t lineLength) +{ + ParseFolderLine(line, lineLength); + return NS_OK; +} + +nsresult nsParseMailMessageState::ParseFolderLine(const char *line, uint32_t lineLength) +{ + nsresult rv; + + if (m_state == nsIMsgParseMailMsgState::ParseHeadersState) + { + if (EMPTY_MESSAGE_LINE(line)) + { + /* End of headers. Now parse them. */ + rv = ParseHeaders(); + NS_ASSERTION(NS_SUCCEEDED(rv), "error parsing headers parsing mailbox"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = FinalizeHeaders(); + NS_ASSERTION(NS_SUCCEEDED(rv), "error finalizing headers parsing mailbox"); + NS_ENSURE_SUCCESS(rv, rv); + + m_state = nsIMsgParseMailMsgState::ParseBodyState; + } + else + { + /* Otherwise, this line belongs to a header. So append it to the + header data, and stay in MBOX `MIME_PARSE_HEADERS' state. + */ + m_headers.AppendBuffer(line, lineLength); + } + } + else if ( m_state == nsIMsgParseMailMsgState::ParseBodyState) + { + m_body_lines++; + } + + m_position += lineLength; + + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetMailDB(nsIMsgDatabase *mailDB) +{ + m_mailDB = mailDB; + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetBackupMailDB(nsIMsgDatabase *aBackupMailDB) +{ + m_backupMailDB = aBackupMailDB; + if (m_backupMailDB) + m_backupMailDB->AddListener(this); + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::SetNewKey(nsMsgKey aKey) +{ + m_new_key = aKey; + return NS_OK; +} + +/* #define STRICT_ENVELOPE */ + +bool +nsParseMailMessageState::IsEnvelopeLine(const char *buf, int32_t buf_size) +{ +#ifdef STRICT_ENVELOPE + /* The required format is + From jwz Fri Jul 1 09:13:09 1994 + But we should also allow at least: + From jwz Fri, Jul 01 09:13:09 1994 + From jwz Fri Jul 1 09:13:09 1994 PST + From jwz Fri Jul 1 09:13:09 1994 (+0700) + + We can't easily call XP_ParseTimeString() because the string is not + null terminated (ok, we could copy it after a quick check...) but + XP_ParseTimeString() may be too lenient for our purposes. + + DANGER!! The released version of 2.0b1 was (on some systems, + some Unix, some NT, possibly others) writing out envelope lines + like "From - 10/13/95 11:22:33" which STRICT_ENVELOPE will reject! + */ + const char *date, *end; + + if (buf_size < 29) return false; + if (*buf != 'F') return false; + if (strncmp(buf, "From ", 5)) return false; + + end = buf + buf_size; + date = buf + 5; + + /* Skip horizontal whitespace between "From " and user name. */ + while ((*date == ' ' || *date == '\t') && date < end) + date++; + + /* If at the end, it doesn't match. */ + if (IS_SPACE(*date) || date == end) + return false; + + /* Skip over user name. */ + while (!IS_SPACE(*date) && date < end) + date++; + + /* Skip horizontal whitespace between user name and date. */ + while ((*date == ' ' || *date == '\t') && date < end) + date++; + + /* Don't want this to be localized. */ +# define TMP_ISALPHA(x) (((x) >= 'A' && (x) <= 'Z') || \ + ((x) >= 'a' && (x) <= 'z')) + + /* take off day-of-the-week. */ + if (date >= end - 3) + return false; + if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2])) + return false; + date += 3; + /* Skip horizontal whitespace (and commas) between dotw and month. */ + if (*date != ' ' && *date != '\t' && *date != ',') + return false; + while ((*date == ' ' || *date == '\t' || *date == ',') && date < end) + date++; + + /* take off month. */ + if (date >= end - 3) + return false; + if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2])) + return false; + date += 3; + /* Skip horizontal whitespace between month and dotm. */ + if (date == end || (*date != ' ' && *date != '\t')) + return false; + while ((*date == ' ' || *date == '\t') && date < end) + date++; + + /* Skip over digits and whitespace. */ + while (((*date >= '0' && *date <= '9') || *date == ' ' || *date == '\t') && + date < end) + date++; + /* Next character should be a colon. */ + if (date >= end || *date != ':') + return false; + + /* Ok, that ought to be enough... */ + +# undef TMP_ISALPHA + +#else /* !STRICT_ENVELOPE */ + + if (buf_size < 5) return false; + if (*buf != 'F') return false; + if (strncmp(buf, "From ", 5)) return false; + +#endif /* !STRICT_ENVELOPE */ + + return true; +} + +// We've found the start of the next message, so finish this one off. +NS_IMETHODIMP nsParseMailMessageState::FinishHeader() +{ + if (m_newMsgHdr) + { + m_newMsgHdr->SetMessageOffset(m_envelope_pos); + m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos); + m_newMsgHdr->SetLineCount(m_body_lines); + } + return NS_OK; +} + +NS_IMETHODIMP nsParseMailMessageState::GetAllHeaders(char ** pHeaders, int32_t *pHeadersSize) +{ + if (!pHeaders || !pHeadersSize) + return NS_ERROR_NULL_POINTER; + *pHeaders = m_headers.GetBuffer(); + *pHeadersSize = m_headers.GetBufferPos(); + return NS_OK; +} + +// generate headers as a string, with CRLF between the headers +NS_IMETHODIMP nsParseMailMessageState::GetHeaders(char ** pHeaders) +{ + NS_ENSURE_ARG_POINTER(pHeaders); + nsCString crlfHeaders; + char *curHeader = m_headers.GetBuffer(); + for (uint32_t headerPos = 0; headerPos < m_headers.GetBufferPos();) + { + crlfHeaders.Append(curHeader); + crlfHeaders.Append(CRLF); + int32_t headerLen = strlen(curHeader); + curHeader += headerLen + 1; + headerPos += headerLen + 1; + } + *pHeaders = ToNewCString(crlfHeaders); + return NS_OK; +} + +struct message_header *nsParseMailMessageState::GetNextHeaderInAggregate (nsTArray<struct message_header*> &list) +{ + // When parsing a message with multiple To or CC header lines, we're storing each line in a + // list, where the list represents the "aggregate" total of all the header. Here we get a new + // line for the list + + struct message_header *header = (struct message_header*) PR_Calloc (1, sizeof(struct message_header)); + list.AppendElement (header); + return header; +} + +void nsParseMailMessageState::GetAggregateHeader (nsTArray<struct message_header*> &list, struct message_header *outHeader) +{ + // When parsing a message with multiple To or CC header lines, we're storing each line in a + // list, where the list represents the "aggregate" total of all the header. Here we combine + // all the lines together, as though they were really all found on the same line + + struct message_header *header = nullptr; + int length = 0; + size_t i; + + // Count up the bytes required to allocate the aggregated header + for (i = 0; i < list.Length(); i++) + { + header = list.ElementAt(i); + length += (header->length + 1); //+ for "," + } + + if (length > 0) + { + char *value = (char*) PR_CALLOC (length + 1); //+1 for null term + if (value) + { + // Catenate all the To lines together, separated by commas + value[0] = '\0'; + size_t size = list.Length(); + for (i = 0; i < size; i++) + { + header = list.ElementAt(i); + PL_strncat (value, header->value, header->length); + if (i + 1 < size) + PL_strcat (value, ","); + } + outHeader->length = length; + outHeader->value = value; + } + } + else + { + outHeader->length = 0; + outHeader->value = nullptr; + } +} + +void nsParseMailMessageState::ClearAggregateHeader (nsTArray<struct message_header*> &list) +{ + // Reset the aggregate headers. Free only the message_header struct since + // we don't own the value pointer + + for (size_t i = 0; i < list.Length(); i++) + PR_Free (list.ElementAt(i)); + list.Clear(); +} + +// We've found a new envelope to parse. +nsresult nsParseMailMessageState::StartNewEnvelope(const char *line, uint32_t lineLength) +{ + m_envelope_pos = m_position; + m_state = nsIMsgParseMailMsgState::ParseHeadersState; + m_position += lineLength; + m_headerstartpos = m_position; + return ParseEnvelope (line, lineLength); +} + +/* largely lifted from mimehtml.c, which does similar parsing, sigh... +*/ +nsresult nsParseMailMessageState::ParseHeaders () +{ + char *buf = m_headers.GetBuffer(); + uint32_t buf_length = m_headers.GetBufferPos(); + if (buf_length == 0) + { + // No header of an expected type is present. Consider this a successful + // parse so email still shows on summary and can be accessed and deleted. + return NS_OK; + } + char *buf_end = buf + buf_length; + if (!(buf_length > 1 && (buf[buf_length - 1] == '\r' || + buf[buf_length - 1] == '\n'))) + { + NS_WARNING("Header text should always end in a newline"); + return NS_ERROR_UNEXPECTED; + } + while (buf < buf_end) + { + char *colon = PL_strnchr(buf, ':', buf_end - buf); + char *end; + char *value = 0; + struct message_header *header = 0; + struct message_header receivedBy; + + if (!colon) + break; + + end = colon; + + switch (buf [0]) + { + case 'B': case 'b': + if (!PL_strncasecmp ("BCC", buf, end - buf)) + header = &m_bccList; + break; + case 'C': case 'c': + if (!PL_strncasecmp ("CC", buf, end - buf)) + header = GetNextHeaderInAggregate(m_ccList); + else if (!PL_strncasecmp ("Content-Type", buf, end - buf)) + header = &m_content_type; + break; + case 'D': case 'd': + if (!PL_strncasecmp ("Date", buf, end - buf)) + header = &m_date; + else if (!PL_strncasecmp("Disposition-Notification-To", buf, end - buf)) + header = &m_mdn_dnt; + else if (!PL_strncasecmp("Delivery-date", buf, end - buf)) + header = &m_delivery_date; + break; + case 'F': case 'f': + if (!PL_strncasecmp ("From", buf, end - buf)) + header = &m_from; + break; + case 'I' : case 'i': + if (!PL_strncasecmp ("In-Reply-To", buf, end - buf)) + header = &m_in_reply_to; + break; + case 'M': case 'm': + if (!PL_strncasecmp ("Message-ID", buf, end - buf)) + header = &m_message_id; + break; + case 'N': case 'n': + if (!PL_strncasecmp ("Newsgroups", buf, end - buf)) + header = &m_newsgroups; + break; + case 'O': case 'o': + if (!PL_strncasecmp ("Original-Recipient", buf, end - buf)) + header = &m_mdn_original_recipient; + break; + case 'R': case 'r': + if (!PL_strncasecmp ("References", buf, end - buf)) + header = &m_references; + else if (!PL_strncasecmp ("Return-Path", buf, end - buf)) + header = &m_return_path; + // treat conventional Return-Receipt-To as MDN + // Disposition-Notification-To + else if (!PL_strncasecmp ("Return-Receipt-To", buf, end - buf)) + header = &m_mdn_dnt; + else if (!PL_strncasecmp("Reply-To", buf, end - buf)) + header = &m_replyTo; + else if (!PL_strncasecmp("Received", buf, end - buf)) + { + header = &receivedBy; + header->length = 0; + } + break; + case 'S': case 's': + if (!PL_strncasecmp ("Subject", buf, end - buf) && !m_subject.length) + header = &m_subject; + else if (!PL_strncasecmp ("Sender", buf, end - buf)) + header = &m_sender; + else if (!PL_strncasecmp ("Status", buf, end - buf)) + header = &m_status; + break; + case 'T': case 't': + if (!PL_strncasecmp ("To", buf, end - buf)) + header = GetNextHeaderInAggregate(m_toList); + break; + case 'X': + if (X_MOZILLA_STATUS2_LEN == end - buf && + !PL_strncasecmp(X_MOZILLA_STATUS2, buf, end - buf) && + !m_IgnoreXMozillaStatus && !m_mozstatus2.length) + header = &m_mozstatus2; + else if ( X_MOZILLA_STATUS_LEN == end - buf && + !PL_strncasecmp(X_MOZILLA_STATUS, buf, end - buf) && !m_IgnoreXMozillaStatus + && !m_mozstatus.length) + header = &m_mozstatus; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf, end - buf) + && !m_account_key.length) + header = &m_account_key; + // we could very well care what the priority header was when we + // remember its value. If so, need to remember it here. Also, + // different priority headers can appear in the same message, + // but we only rememeber the last one that we see. + else if (!PL_strncasecmp("X-Priority", buf, end - buf) + || !PL_strncasecmp("Priority", buf, end - buf)) + header = &m_priority; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf) + && !m_keywords.length) + header = &m_keywords; + break; + } + if (!header && m_customDBHeaders.Length()) + { +#ifdef MOZILLA_INTERNAL_API + nsDependentCSubstring headerStr(buf, end); +#else + nsDependentCSubstring headerStr(buf, end - buf); +#endif + + ToLowerCase(headerStr); + size_t customHeaderIndex = m_customDBHeaders.IndexOf(headerStr); + if (customHeaderIndex != m_customDBHeaders.NoIndex) + header = & m_customDBHeaderValues[customHeaderIndex]; + } + + buf = colon + 1; + uint32_t writeOffset = 0; // number of characters replaced with a folded space + +SEARCH_NEWLINE: + // move past any non terminating characters, rewriting them if folding white space + // exists + while (buf < buf_end && *buf != '\r' && *buf != '\n') + { + if (writeOffset) + *(buf - writeOffset) = *buf; + buf++; + } + + /* If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. */ + if ((buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') && + (buf[2] == ' ' || buf[2] == '\t')) || + /* If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate + the header either. */ + (buf + 1 < buf_end && (buf[0] == '\r' || buf[0] == '\n') && + (buf[1] == ' ' || buf[1] == '\t'))) + { + // locate the proper location for a folded space by eliminating any + // leading spaces before the end-of-line character + char* foldedSpace = buf; + while (*(foldedSpace - 1) == ' ' || *(foldedSpace - 1) == '\t') + foldedSpace--; + + // put a single folded space character + *(foldedSpace - writeOffset) = ' '; + writeOffset += (buf - foldedSpace); + buf++; + + // eliminate any additional white space + while (buf < buf_end && + (*buf == '\n' || *buf == '\r' || *buf == ' ' || *buf == '\t')) + { + buf++; + writeOffset++; + } + + // If we get here, the message headers ended in an empty line, like: + // To: blah blah blah<CR><LF> <CR><LF>[end of buffer]. The code below + // requires buf to land on a newline to properly null-terminate the + // string, so back up a tad so that it is pointing to one. + if (buf == buf_end) + { + --buf; + MOZ_ASSERT(*buf == '\n' || *buf == '\r', + "Header text should always end in a newline."); + } + goto SEARCH_NEWLINE; + } + + if (header) + { + value = colon + 1; + // eliminate trailing blanks after the colon + while (value < (buf - writeOffset) && (*value == ' ' || *value == '\t')) + value++; + + header->value = value; + header->length = buf - header->value - writeOffset; + if (header->length < 0) + header->length = 0; + } + if (*buf == '\r' || *buf == '\n') + { + char *last = buf - writeOffset; + char *saveBuf = buf; + if (*buf == '\r' && buf + 1 < buf_end && buf[1] == '\n') + buf++; + buf++; + // null terminate the left-over slop so we don't confuse msg filters. + *saveBuf = 0; + *last = 0; /* short-circuit const, and null-terminate header. */ + } + + if (header) + { + /* More const short-circuitry... */ + /* strip trailing whitespace */ + while (header->length > 0 && + IS_SPACE (header->value [header->length - 1])) + ((char *) header->value) [--header->length] = 0; + if (header == &receivedBy) + { + if (m_receivedTime == 0) + { + // parse Received: header for date. + // We trust the first header as that is closest to recipient, + // and less likely to be spoofed. + nsAutoCString receivedHdr(header->value, header->length); + int32_t lastSemicolon = receivedHdr.RFindChar(';'); + if (lastSemicolon != -1) + { + nsAutoCString receivedDate; + receivedDate = Substring(receivedHdr, lastSemicolon + 1); + receivedDate.Trim(" \t\b\r\n"); + PRTime resultTime; + if (PR_ParseTimeString (receivedDate.get(), false, &resultTime) == PR_SUCCESS) + m_receivedTime = resultTime; + else + NS_WARNING("PR_ParseTimeString failed in ParseHeaders()."); + } + } + // Someone might want the received header saved. + if (m_customDBHeaders.Length()) + { + if (m_customDBHeaders.Contains(NS_LITERAL_CSTRING("received"))) + { + if (!m_receivedValue.IsEmpty()) + m_receivedValue.Append(' '); + m_receivedValue.Append(header->value, header->length); + } + } + } + + MOZ_ASSERT(header->value[header->length] == 0, + "Non-null-terminated strings cause very, very bad problems"); + } + } + return NS_OK; +} + +nsresult nsParseMailMessageState::ParseEnvelope (const char *line, uint32_t line_size) +{ + const char *end; + char *s; + + m_envelope.AppendBuffer(line, line_size); + end = m_envelope.GetBuffer() + line_size; + s = m_envelope.GetBuffer() + 5; + + while (s < end && IS_SPACE (*s)) + s++; + m_envelope_from.value = s; + while (s < end && !IS_SPACE (*s)) + s++; + m_envelope_from.length = s - m_envelope_from.value; + + while (s < end && IS_SPACE (*s)) + s++; + m_envelope_date.value = s; + m_envelope_date.length = (uint16_t) (line_size - (s - m_envelope.GetBuffer())); + + while (m_envelope_date.length > 0 && + IS_SPACE (m_envelope_date.value [m_envelope_date.length - 1])) + m_envelope_date.length--; + + /* #### short-circuit const */ + ((char *) m_envelope_from.value) [m_envelope_from.length] = 0; + ((char *) m_envelope_date.value) [m_envelope_date.length] = 0; + + return NS_OK; +} + +nsresult nsParseMailMessageState::InternSubject (struct message_header *header) +{ + if (!header || header->length == 0) + { + m_newMsgHdr->SetSubject(""); + return NS_OK; + } + + const char *key = header->value; + + uint32_t flags; + (void)m_newMsgHdr->GetFlags(&flags); + /* strip "Re: " */ + /** + We trust the X-Mozilla-Status line to be the smartest in almost + all things. One exception, however, is the HAS_RE flag. Since + we just parsed the subject header anyway, we expect that parsing + to be smartest. (After all, what if someone just went in and + edited the subject line by hand?) + */ + nsCString modifiedSubject; + if (NS_MsgStripRE(nsDependentCString(key), modifiedSubject)) + flags |= nsMsgMessageFlags::HasRe; + else + flags &= ~nsMsgMessageFlags::HasRe; + m_newMsgHdr->SetFlags(flags); // this *does not* update the mozilla-status header in the local folder + + // Condense the subject text into as few MIME-2 encoded words as possible. + m_newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? key : modifiedSubject.get()); + + return NS_OK; +} + +// we've reached the end of the envelope, and need to turn all our accumulated message_headers +// into a single nsIMsgDBHdr to store in a database. +nsresult nsParseMailMessageState::FinalizeHeaders() +{ + nsresult rv; + struct message_header *sender; + struct message_header *recipient; + struct message_header *subject; + struct message_header *id; + struct message_header *inReplyTo; + struct message_header *replyTo; + struct message_header *references; + struct message_header *date; + struct message_header *deliveryDate; + struct message_header *statush; + struct message_header *mozstatus; + struct message_header *mozstatus2; + struct message_header *priority; + struct message_header *keywords; + struct message_header *account_key; + struct message_header *ccList; + struct message_header *bccList; + struct message_header *mdn_dnt; + struct message_header md5_header; + struct message_header *content_type; + char md5_data [50]; + + uint32_t flags = 0; + uint32_t delta = 0; + nsMsgPriorityValue priorityFlags = nsMsgPriority::notSet; + uint32_t labelFlags = 0; + + if (!m_mailDB) // if we don't have a valid db, skip the header. + return NS_OK; + + struct message_header to; + GetAggregateHeader (m_toList, &to); + struct message_header cc; + GetAggregateHeader (m_ccList, &cc); + // we don't aggregate bcc, as we only generate it locally, + // and we don't use multiple lines + + sender = (m_from.length ? &m_from : + m_sender.length ? &m_sender : + m_envelope_from.length ? &m_envelope_from : + 0); + recipient = (to.length ? &to : + cc.length ? &cc : + m_newsgroups.length ? &m_newsgroups : + 0); + ccList = (cc.length ? &cc : 0); + bccList = (m_bccList.length ? &m_bccList : 0); + subject = (m_subject.length ? &m_subject : 0); + id = (m_message_id.length ? &m_message_id : 0); + references = (m_references.length ? &m_references : 0); + statush = (m_status.length ? &m_status : 0); + mozstatus = (m_mozstatus.length ? &m_mozstatus : 0); + mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0); + date = (m_date.length ? &m_date : + m_envelope_date.length ? &m_envelope_date : + 0); + deliveryDate = (m_delivery_date.length ? &m_delivery_date : 0); + priority = (m_priority.length ? &m_priority : 0); + keywords = (m_keywords.length ? &m_keywords : 0); + mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0); + inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0); + replyTo = (m_replyTo.length ? &m_replyTo : 0); + content_type = (m_content_type.length ? &m_content_type : 0); + account_key = (m_account_key.length ? &m_account_key :0); + + if (mozstatus) + { + if (mozstatus->length == 4) + { + NS_ASSERTION(MsgIsHex(mozstatus->value, 4), "Expected 4 hex digits for flags."); + flags = MsgUnhex(mozstatus->value, 4); + // strip off and remember priority bits. + flags &= ~nsMsgMessageFlags::RuntimeOnly; + priorityFlags = (nsMsgPriorityValue) ((flags & nsMsgMessageFlags::Priorities) >> 13); + flags &= ~nsMsgMessageFlags::Priorities; + } + delta = (m_headerstartpos + + (mozstatus->value - m_headers.GetBuffer()) - + (2 + X_MOZILLA_STATUS_LEN) /* 2 extra bytes for ": ". */ + ) - m_envelope_pos; + } + + if (mozstatus2) + { + uint32_t flags2 = 0; + sscanf(mozstatus2->value, " %x ", &flags2); + flags |= flags2; + } + + if (!(flags & nsMsgMessageFlags::Expunged)) // message was deleted, don't bother creating a hdr. + { + // We'll need the message id first to recover data from the backup database + nsAutoCString rawMsgId; + /* Take off <> around message ID. */ + if (id) + { + if (id->length > 0 && id->value[0] == '<') + id->length--, id->value++; + + NS_WARNING_ASSERTION(id->length > 0, "id->length failure in FinalizeHeaders()."); + + if (id->length > 0 && id->value[id->length - 1] == '>') + /* generate a new null-terminated string without the final > */ + rawMsgId.Assign(id->value, id->length - 1); + else + rawMsgId.Assign(id->value); + } + + /* + * Try to copy the data from the backup database, referencing the MessageID + * If that fails, just create a new header + */ + nsCOMPtr<nsIMsgDBHdr> oldHeader; + nsresult ret = NS_OK; + + if (m_backupMailDB && !rawMsgId.IsEmpty()) + ret = m_backupMailDB->GetMsgHdrForMessageID( + rawMsgId.get(), getter_AddRefs(oldHeader)); + + // m_new_key is set in nsImapMailFolder::ParseAdoptedHeaderLine to be + // the UID of the message, so that the key can get created as UID. That of + // course is extremely confusing, and we really need to clean that up. We + // really should not conflate the meaning of envelope position, key, and + // UID. + if (NS_SUCCEEDED(ret) && oldHeader) + ret = m_mailDB->CopyHdrFromExistingHdr(m_new_key, + oldHeader, false, getter_AddRefs(m_newMsgHdr)); + else if (!m_newMsgHdr) + { + // Should assert that this is not a local message + ret = m_mailDB->CreateNewHdr(m_new_key, getter_AddRefs(m_newMsgHdr)); + } + + if (NS_SUCCEEDED(ret) && m_newMsgHdr) + { + uint32_t origFlags; + (void)m_newMsgHdr->GetFlags(&origFlags); + if (origFlags & nsMsgMessageFlags::HasRe) + flags |= nsMsgMessageFlags::HasRe; + else + flags &= ~nsMsgMessageFlags::HasRe; + + flags &= ~nsMsgMessageFlags::Offline; // don't keep nsMsgMessageFlags::Offline for local msgs + if (mdn_dnt && !(origFlags & nsMsgMessageFlags::Read) && + !(origFlags & nsMsgMessageFlags::MDNReportSent) && + !(flags & nsMsgMessageFlags::MDNReportSent)) + flags |= nsMsgMessageFlags::MDNReportNeeded; + + m_newMsgHdr->SetFlags(flags); + if (priorityFlags != nsMsgPriority::notSet) + m_newMsgHdr->SetPriority(priorityFlags); + + // if we have a reply to header, and it's different from the from: header, + // set the "replyTo" attribute on the msg hdr. + if (replyTo && (!sender || replyTo->length != sender->length || strncmp(replyTo->value, sender->value, sender->length))) + m_newMsgHdr->SetStringProperty("replyTo", replyTo->value); + // convert the flag values (0xE000000) to label values (0-5) + if (mozstatus2) // only do this if we have a mozstatus2 header + { + labelFlags = ((flags & nsMsgMessageFlags::Labels) >> 25); + m_newMsgHdr->SetLabel(labelFlags); + } + if (delta < 0xffff) + { /* Only use if fits in 16 bits. */ + m_newMsgHdr->SetStatusOffset((uint16_t) delta); + if (!m_IgnoreXMozillaStatus) { // imap doesn't care about X-MozillaStatus + uint32_t offset; + (void)m_newMsgHdr->GetStatusOffset(&offset); + NS_ASSERTION(offset < 10000, "invalid status offset"); /* ### Debugging hack */ + } + } + if (sender) + m_newMsgHdr->SetAuthor(sender->value); + if (recipient == &m_newsgroups) + { + /* In the case where the recipient is a newsgroup, truncate the string + at the first comma. This is used only for presenting the thread list, + and newsgroup lines tend to be long and non-shared, and tend to bloat + the string table. So, by only showing the first newsgroup, we can + reduce memory and file usage at the expense of only showing the one + group in the summary list, and only being able to sort on the first + group rather than the whole list. It's worth it. */ + char * ch; + ch = PL_strchr(recipient->value, ','); + if (ch) + { + /* generate a new string that terminates before the , */ + nsAutoCString firstGroup; + firstGroup.Assign(recipient->value, ch - recipient->value); + m_newMsgHdr->SetRecipients(firstGroup.get()); + } + m_newMsgHdr->SetRecipients(recipient->value); + } + else if (recipient) + { + m_newMsgHdr->SetRecipients(recipient->value); + } + if (ccList) + { + m_newMsgHdr->SetCcList(ccList->value); + } + + if (bccList) + { + m_newMsgHdr->SetBccList(bccList->value); + } + + rv = InternSubject (subject); + if (NS_SUCCEEDED(rv)) + { + if (!id) + { + // what to do about this? we used to do a hash of all the headers... + nsAutoCString hash; + const char *md5_b64 = "dummy.message.id"; + nsresult rv; + nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + if (NS_SUCCEEDED(rv)) + { + if (NS_SUCCEEDED(hasher->Init(nsICryptoHash::MD5)) && + NS_SUCCEEDED(hasher->Update((const uint8_t*) m_headers.GetBuffer(), m_headers.GetSize())) && + NS_SUCCEEDED(hasher->Finish(true, hash))) + md5_b64 = hash.get(); + } + PR_snprintf (md5_data, sizeof(md5_data), "<md5:%s>", md5_b64); + md5_header.value = md5_data; + md5_header.length = strlen(md5_data); + id = &md5_header; + } + + if (!rawMsgId.IsEmpty()) + m_newMsgHdr->SetMessageId(rawMsgId.get()); + else + m_newMsgHdr->SetMessageId(id->value); + m_mailDB->UpdatePendingAttributes(m_newMsgHdr); + + if (!mozstatus && statush) + { + /* Parse a little bit of the Berkeley Mail status header. */ + for (const char *s = statush->value; *s; s++) { + uint32_t msgFlags = 0; + (void)m_newMsgHdr->GetFlags(&msgFlags); + switch (*s) + { + case 'R': case 'r': + m_newMsgHdr->SetFlags(msgFlags | nsMsgMessageFlags::Read); + break; + case 'D': case 'd': + /* msg->flags |= nsMsgMessageFlags::Expunged; ### Is this reasonable? */ + break; + case 'N': case 'n': + case 'U': case 'u': + m_newMsgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::Read); + break; + default: // Should check for corrupt file. + NS_ERROR("Corrupt file. Should not happen."); + break; + } + } + } + + if (account_key != nullptr) + m_newMsgHdr->SetAccountKey(account_key->value); + // use in-reply-to header as references, if there's no references header + if (references != nullptr) + m_newMsgHdr->SetReferences(references->value); + else if (inReplyTo != nullptr) + m_newMsgHdr->SetReferences(inReplyTo->value); + + // 'Received' should be as reliable an indicator of the receipt + // date+time as possible, whilst always giving something *from + // the message*. It won't use PR_Now() under any circumstance. + // Therefore, the fall-thru order for 'Received' is: + // Received: -> Delivery-date: -> date + // 'Date' uses: + // date -> PR_Now() + // + // date is: + // Date: -> m_envelope_date + + uint32_t rcvTimeSecs = 0; + if (date) + { // Date: + PRTime resultTime; + PRStatus timeStatus = PR_ParseTimeString (date->value, false, &resultTime); + if (PR_SUCCESS == timeStatus) + { + m_newMsgHdr->SetDate(resultTime); + PRTime2Seconds(resultTime, &rcvTimeSecs); + } + else + NS_WARNING("PR_ParseTimeString of date failed in FinalizeHeader()."); + } + else + { // PR_Now() + // If there was some problem parsing the Date header *AND* we + // couldn't get a valid envelope date, use now as the time. + // PR_ParseTimeString won't touch resultTime unless it succeeds. + // This doesn't affect local (POP3) messages, because we use the envelope + // date if there's no Date: header, but it will affect IMAP msgs + // w/o a Date: hdr or Received: headers. + PRTime resultTime = PR_Now(); + m_newMsgHdr->SetDate(resultTime); + } + if (m_receivedTime != 0) + { // Upgrade 'Received' to Received: ? + PRTime2Seconds(m_receivedTime, &rcvTimeSecs); + } + else if (deliveryDate) + { // Upgrade 'Received' to Delivery-date: ? + PRTime resultTime; + PRStatus timeStatus = PR_ParseTimeString (deliveryDate->value, false, &resultTime); + if (PR_SUCCESS == timeStatus) + PRTime2Seconds(resultTime, &rcvTimeSecs); + else // TODO/FIXME: We need to figure out what to do in this case! + NS_WARNING("PR_ParseTimeString of delivery date failed in FinalizeHeader()."); + } + m_newMsgHdr->SetUint32Property("dateReceived", rcvTimeSecs); + + if (priority) + m_newMsgHdr->SetPriorityString(priority->value); + else if (priorityFlags == nsMsgPriority::notSet) + m_newMsgHdr->SetPriority(nsMsgPriority::none); + if (keywords) + { + // When there are many keywords, some may not have been written + // to the message file, so add extra keywords from the backup + nsAutoCString oldKeywords; + m_newMsgHdr->GetStringProperty("keywords", getter_Copies(oldKeywords)); + nsTArray<nsCString> newKeywordArray, oldKeywordArray; + ParseString(Substring(keywords->value, keywords->value + keywords->length), ' ', newKeywordArray); + ParseString(oldKeywords, ' ', oldKeywordArray); + for (uint32_t i = 0; i < oldKeywordArray.Length(); i++) + if (!newKeywordArray.Contains(oldKeywordArray[i])) + newKeywordArray.AppendElement(oldKeywordArray[i]); + nsAutoCString newKeywords; + for (uint32_t i = 0; i < newKeywordArray.Length(); i++) + { + if (i) + newKeywords.Append(" "); + newKeywords.Append(newKeywordArray[i]); + } + m_newMsgHdr->SetStringProperty("keywords", newKeywords.get()); + } + for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) + { + if (m_customDBHeaderValues[i].length) + m_newMsgHdr->SetStringProperty(m_customDBHeaders[i].get(), m_customDBHeaderValues[i].value); + // The received header is accumulated separately + if (m_customDBHeaders[i].EqualsLiteral("received") && !m_receivedValue.IsEmpty()) + m_newMsgHdr->SetStringProperty("received", m_receivedValue.get()); + } + if (content_type) + { + char *substring = PL_strstr(content_type->value, "charset"); + if (substring) + { + char *charset = PL_strchr (substring, '='); + if (charset) + { + charset++; + /* strip leading whitespace and double-quote */ + while (*charset && (IS_SPACE (*charset) || '\"' == *charset)) + charset++; + /* strip trailing whitespace and double-quote */ + char *end = charset; + while (*end && !IS_SPACE (*end) && '\"' != *end && ';' != *end) + end++; + if (*charset) + { + if (*end != '\0') { + // if we're not at the very end of the line, we need + // to generate a new string without the trailing crud + nsAutoCString rawCharSet; + rawCharSet.Assign(charset, end - charset); + m_newMsgHdr->SetCharset(rawCharSet.get()); + } else { + m_newMsgHdr->SetCharset(charset); + } + } + } + } + substring = PL_strcasestr(content_type->value, "multipart/mixed"); + if (substring) + { + uint32_t newFlags; + m_newMsgHdr->OrFlags(nsMsgMessageFlags::Attachment, &newFlags); + } + } + } + } + else + { + NS_ASSERTION(false, "error creating message header"); + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + else + rv = NS_OK; + + //### why is this stuff const? + char *tmp = (char*) to.value; + PR_Free(tmp); + tmp = (char*) cc.value; + PR_Free(tmp); + + return rv; +} + +nsParseNewMailState::nsParseNewMailState() + : m_disableFilters(false) +{ + m_ibuffer = nullptr; + m_ibuffer_size = 0; + m_ibuffer_fp = 0; + m_numNotNewMessages = 0; + } + +NS_IMPL_ISUPPORTS_INHERITED(nsParseNewMailState, nsMsgMailboxParser, nsIMsgFilterHitNotify) + +nsresult +nsParseNewMailState::Init(nsIMsgFolder *serverFolder, nsIMsgFolder *downloadFolder, + nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aHdr, + nsIOutputStream *aOutputStream) +{ + nsresult rv; + Clear(); + m_rootFolder = serverFolder; + m_msgWindow = aMsgWindow; + m_downloadFolder = downloadFolder; + + m_newMsgHdr = aHdr; + m_outputStream = aOutputStream; + // the new mail parser isn't going to get the stream input, it seems, so we can't use + // the OnStartRequest mechanism the mailbox parser uses. So, let's open the db right now. + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService && !m_mailDB) + rv = msgDBService->OpenFolderDB(downloadFolder, false, + getter_AddRefs(m_mailDB)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgFolder> rootMsgFolder = do_QueryInterface(serverFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = rootMsgFolder->GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv)) + { + rv = server->GetFilterList(aMsgWindow, getter_AddRefs(m_filterList)); + + if (m_filterList) + rv = server->ConfigureTemporaryFilters(m_filterList); + // check if this server defers to another server, in which case + // we'll use that server's filters as well. + nsCOMPtr <nsIMsgFolder> deferredToRootFolder; + server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder)); + if (rootMsgFolder != deferredToRootFolder) + { + nsCOMPtr <nsIMsgIncomingServer> deferredToServer; + deferredToRootFolder->GetServer(getter_AddRefs(deferredToServer)); + if (deferredToServer) + deferredToServer->GetFilterList(aMsgWindow, getter_AddRefs(m_deferredToServerFilterList)); + } + } + m_disableFilters = false; + return NS_OK; +} + +nsParseNewMailState::~nsParseNewMailState() +{ + if (m_mailDB) + m_mailDB->Close(true); + if (m_backupMailDB) + m_backupMailDB->ForceClosed(); +#ifdef DOING_JSFILTERS + JSFilter_cleanup(); +#endif +} + +// not an IMETHOD so we don't need to do error checking or return an error. +// We only have one caller. +void nsParseNewMailState::GetMsgWindow(nsIMsgWindow **aMsgWindow) +{ + NS_IF_ADDREF(*aMsgWindow = m_msgWindow); +} + + +// This gets called for every message because libnet calls IncorporateBegin, +// IncorporateWrite (once or more), and IncorporateComplete for every message. +void nsParseNewMailState::DoneParsingFolder(nsresult status) +{ + /* End of file. Flush out any partial line remaining in the buffer. */ + if (m_ibuffer_fp > 0) + { + ParseFolderLine(m_ibuffer, m_ibuffer_fp); + m_ibuffer_fp = 0; + } + PublishMsgHeader(nullptr); + if (m_mailDB) // finished parsing, so flush db folder info + UpdateDBFolderInfo(); + + /* We're done reading the folder - we don't need these things + any more. */ + PR_FREEIF (m_ibuffer); + m_ibuffer_size = 0; + PR_FREEIF (m_obuffer); + m_obuffer_size = 0; +} + +void nsParseNewMailState::OnNewMessage(nsIMsgWindow *msgWindow) +{ +} + +int32_t nsParseNewMailState::PublishMsgHeader(nsIMsgWindow *msgWindow) +{ + bool moved = false; + FinishHeader(); + + if (m_newMsgHdr) + { + uint32_t newFlags, oldFlags; + m_newMsgHdr->GetFlags(&oldFlags); + if (!(oldFlags & nsMsgMessageFlags::Read)) // don't mark read messages as new. + m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + + if (!m_disableFilters) + { + uint64_t msgOffset; + (void) m_newMsgHdr->GetMessageOffset(&msgOffset); + m_curHdrOffset = msgOffset; + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = m_rootFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, 0); + int32_t duplicateAction; + server->GetIncomingDuplicateAction(&duplicateAction); + if (duplicateAction != nsIMsgIncomingServer::keepDups) + { + bool isDup; + server->IsNewHdrDuplicate(m_newMsgHdr, &isDup); + if (isDup) + { + // we want to do something similar to applying filter hits. + // if a dup is marked read, it shouldn't trigger biff. + // Same for deleting it or moving it to trash. + switch (duplicateAction) + { + case nsIMsgIncomingServer::deleteDups: + { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = + m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) + { + rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr); + if (NS_FAILED(rv)) + m_rootFolder->ThrowAlertMsg("dupDeleteFolderTruncateFailed", msgWindow); + } + m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr); + } + break; + + case nsIMsgIncomingServer::moveDupsToTrash: + { + nsCOMPtr <nsIMsgFolder> trash; + GetTrashFolder(getter_AddRefs(trash)); + if (trash) { + uint32_t newFlags; + bool msgMoved; + m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) + rv = msgStore->MoveNewlyDownloadedMessage(m_newMsgHdr, trash, &msgMoved); + if (NS_SUCCEEDED(rv) && !msgMoved) { + rv = MoveIncorporatedMessage(m_newMsgHdr, m_mailDB, trash, + nullptr, msgWindow); + if (NS_SUCCEEDED(rv)) + rv = m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr); + } + if (NS_FAILED(rv)) + NS_WARNING("moveDupsToTrash failed for some reason."); + } + } + break; + case nsIMsgIncomingServer::markDupsRead: + MarkFilteredMessageRead(m_newMsgHdr); + break; + } + int32_t numNewMessages; + m_downloadFolder->GetNumNewMessages(false, &numNewMessages); + m_downloadFolder->SetNumNewMessages(numNewMessages - 1); + + m_newMsgHdr = nullptr; + return 0; + } + } + + ApplyFilters(&moved, msgWindow, msgOffset); + } + if (!moved) + { + if (m_mailDB) + { + m_mailDB->AddNewHdrToDB(m_newMsgHdr, true); + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgAdded(m_newMsgHdr); + // mark the header as not yet reported classified + nsMsgKey msgKey; + m_newMsgHdr->GetMessageKey(&msgKey); + m_downloadFolder->OrProcessingFlags( + msgKey, nsMsgProcessingFlags::NotReportedClassified); + } + } // if it was moved by imap filter, m_parseMsgState->m_newMsgHdr == nullptr + m_newMsgHdr = nullptr; + } + return 0; +} + +// We've found the start of the next message, so finish this one off. +NS_IMETHODIMP nsParseNewMailState::FinishHeader() +{ + if (m_newMsgHdr) + { + m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos); + m_newMsgHdr->SetLineCount(m_body_lines); + } + return NS_OK; +} + +nsresult nsParseNewMailState::GetTrashFolder(nsIMsgFolder **pTrashFolder) +{ + nsresult rv=NS_ERROR_UNEXPECTED; + if (!pTrashFolder) + return NS_ERROR_NULL_POINTER; + + if (m_downloadFolder) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + m_downloadFolder->GetServer(getter_AddRefs(incomingServer)); + nsCOMPtr <nsIMsgFolder> rootMsgFolder; + incomingServer->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + if (rootMsgFolder) + { + rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder); + if (!*pTrashFolder) + rv = NS_ERROR_FAILURE; + } + } + return rv; +} + +void nsParseNewMailState::ApplyFilters(bool *pMoved, nsIMsgWindow *msgWindow, uint64_t msgOffset) +{ + m_msgMovedByFilter = m_msgCopiedByFilter = false; + m_curHdrOffset = msgOffset; + + if (!m_disableFilters) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr; + nsCOMPtr<nsIMsgFolder> downloadFolder = m_downloadFolder; + nsCOMPtr <nsIMsgFolder> rootMsgFolder = do_QueryInterface(m_rootFolder); + if (rootMsgFolder) + { + if (!downloadFolder) + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(downloadFolder)); + if (downloadFolder) + downloadFolder->GetURI(m_inboxUri); + char * headers = m_headers.GetBuffer(); + uint32_t headersSize = m_headers.GetBufferPos(); + if (m_filterList) + (void) m_filterList-> + ApplyFiltersToHdr(nsMsgFilterType::InboxRule, msgHdr, downloadFolder, + m_mailDB, headers, headersSize, this, msgWindow); + if (!m_msgMovedByFilter && m_deferredToServerFilterList) + { + (void) m_deferredToServerFilterList-> + ApplyFiltersToHdr(nsMsgFilterType::InboxRule, msgHdr, downloadFolder, + m_mailDB, headers, headersSize, this, msgWindow); + } + } + } + if (pMoved) + *pMoved = m_msgMovedByFilter; +} + +NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindow *msgWindow, bool *applyMore) +{ + NS_ENSURE_ARG_POINTER(filter); + NS_ENSURE_ARG_POINTER(applyMore); + + uint32_t newFlags; + nsresult rv = NS_OK; + + *applyMore = true; + + nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr; + + nsCOMPtr<nsIArray> filterActionList; + + rv = filter->GetSortedActionList(getter_AddRefs(filterActionList)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numActions; + rv = filterActionList->GetLength(&numActions); + NS_ENSURE_SUCCESS(rv, rv); + + bool loggingEnabled = false; + if (m_filterList && numActions) + m_filterList->GetLoggingEnabled(&loggingEnabled); + + bool msgIsNew = true; + for (uint32_t actionIndex = 0; actionIndex < numActions && *applyMore; actionIndex++) + { + nsCOMPtr<nsIMsgRuleAction> filterAction; + rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction), + getter_AddRefs(filterAction)); + if (NS_FAILED(rv) || !filterAction) + continue; + + nsMsgRuleActionType actionType; + if (NS_SUCCEEDED(filterAction->GetType(&actionType))) + { + if (loggingEnabled) + (void)filter->LogRuleHit(filterAction, msgHdr); + + nsCString actionTargetFolderUri; + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) + { + + rv = filterAction->GetTargetFolderUri(actionTargetFolderUri); + if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) + { + NS_ASSERTION(false, "actionTargetFolderUri is empty"); + continue; + } + } + switch (actionType) + { + case nsMsgFilterAction::Delete: + { + nsCOMPtr <nsIMsgFolder> trash; + // set value to trash folder + rv = GetTrashFolder(getter_AddRefs(trash)); + if (NS_SUCCEEDED(rv) && trash) + rv = trash->GetURI(actionTargetFolderUri); + + msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark read in trash. + msgIsNew = false; + } + + // FALLTHROUGH + MOZ_FALLTHROUGH; + case nsMsgFilterAction::MoveToFolder: + // if moving to a different file, do it. + if (actionTargetFolderUri.get() && !m_inboxUri.Equals(actionTargetFolderUri, + nsCaseInsensitiveCStringComparator())) + { + nsresult err; + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &err)); + NS_ENSURE_SUCCESS(err, err); + nsCOMPtr<nsIRDFResource> res; + err = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res)); + if (NS_FAILED(err)) + return err; + + nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &err)); + if (NS_FAILED(err)) + return err; + bool msgMoved = false; + // If we're moving to an imap folder, or this message has already + // has a pending copy action, use the imap coalescer so that + // we won't truncate the inbox before the copy fires. + if (m_msgCopiedByFilter || + StringBeginsWith(actionTargetFolderUri, NS_LITERAL_CSTRING("imap:"))) + { + if (!m_moveCoalescer) + m_moveCoalescer = new nsImapMoveCoalescer(m_downloadFolder, m_msgWindow); + NS_ENSURE_TRUE(m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY); + nsMsgKey msgKey; + (void) msgHdr->GetMessageKey(&msgKey); + m_moveCoalescer->AddMove(destIFolder, msgKey); + err = NS_OK; + msgIsNew = false; + } + else + { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + err = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(err)) + err = msgStore->MoveNewlyDownloadedMessage(msgHdr, destIFolder, &msgMoved); + if (NS_SUCCEEDED(err) && !msgMoved) + err = MoveIncorporatedMessage(msgHdr, m_mailDB, destIFolder, + filter, msgWindow); + m_msgMovedByFilter = NS_SUCCEEDED(err); + if (!m_msgMovedByFilter /* == NS_FAILED(err) */) + { + // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands. + if (loggingEnabled) + (void) filter->LogRuleHitFail(filterAction, msgHdr, err, "Move failed"); + } + } + } + *applyMore = false; + break; + + case nsMsgFilterAction::CopyToFolder: + { + nsCString uri; + rv = m_rootFolder->GetURI(uri); + + if (!actionTargetFolderUri.IsEmpty() && !actionTargetFolderUri.Equals(uri)) + { + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + + nsCOMPtr<nsIMsgFolder> dstFolder; + nsCOMPtr<nsIMsgCopyService> copyService; + rv = GetExistingFolder(actionTargetFolderUri, + getter_AddRefs(dstFolder)); + if (NS_SUCCEEDED(rv)) { + copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + } + else { + // Let's show a more specific warning. + NS_WARNING("Target Folder does not exist."); + return rv; + } + if (NS_SUCCEEDED(rv)) + rv = copyService->CopyMessages(m_downloadFolder, messageArray, dstFolder, + false, nullptr, msgWindow, false); + + if (NS_FAILED(rv)) { + // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands. + if (loggingEnabled) + (void) filter->LogRuleHitFail(filterAction, msgHdr, rv, "Copy failed"); + } + else + m_msgCopiedByFilter = true; + } + } + break; + case nsMsgFilterAction::MarkRead: + msgIsNew = false; + MarkFilteredMessageRead(msgHdr); + break; + case nsMsgFilterAction::MarkUnread: + msgIsNew = true; + MarkFilteredMessageUnread(msgHdr); + break; + case nsMsgFilterAction::KillThread: + msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored); + break; + case nsMsgFilterAction::KillSubthread: + msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags); + break; + case nsMsgFilterAction::WatchThread: + msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags); + break; + case nsMsgFilterAction::MarkFlagged: + { + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->MarkMessagesFlagged(messageArray, true); + } + break; + case nsMsgFilterAction::ChangePriority: + nsMsgPriorityValue filterPriority; + filterAction->GetPriority(&filterPriority); + msgHdr->SetPriority(filterPriority); + break; + case nsMsgFilterAction::AddTag: + { + nsCString keyword; + filterAction->GetStrValue(keyword); + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->AddKeywordsToMessages(messageArray, keyword); + break; + } + case nsMsgFilterAction::Label: + nsMsgLabelValue filterLabel; + filterAction->GetLabel(&filterLabel); + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + m_mailDB->SetLabel(msgKey, filterLabel); + break; + case nsMsgFilterAction::JunkScore: + { + nsAutoCString junkScoreStr; + int32_t junkScore; + filterAction->GetJunkScore(&junkScore); + junkScoreStr.AppendInt(junkScore); + if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) + msgIsNew = false; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + msgHdr->SetStringProperty("junkscore", junkScoreStr.get()); + msgHdr->SetStringProperty("junkscoreorigin", "filter"); + break; + } + case nsMsgFilterAction::Forward: + { + nsCString forwardTo; + filterAction->GetStrValue(forwardTo); + m_forwardTo.AppendElement(forwardTo); + m_msgToForwardOrReply = msgHdr; + } + break; + case nsMsgFilterAction::Reply: + { + nsCString replyTemplateUri; + filterAction->GetStrValue(replyTemplateUri); + m_replyTemplateUri.AppendElement(replyTemplateUri); + m_msgToForwardOrReply = msgHdr; + m_ruleAction = filterAction; + m_filter = filter; + } + break; + case nsMsgFilterAction::DeleteFromPop3Server: + { + uint32_t flags = 0; + nsCOMPtr <nsIMsgFolder> downloadFolder; + msgHdr->GetFolder(getter_AddRefs(downloadFolder)); + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(downloadFolder); + msgHdr->GetFlags(&flags); + if (localFolder) + { + nsCOMPtr<nsIMutableArray> messages = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + messages->AppendElement(msgHdr, false); + // This action ignores the deleteMailLeftOnServer preference + localFolder->MarkMsgsOnPop3Server(messages, POP3_FORCE_DEL); + + // If this is just a header, throw it away. It's useless now + // that the server copy is being deleted. + if (flags & nsMsgMessageFlags::Partial) + { + m_msgMovedByFilter = true; + msgIsNew = false; + } + } + } + break; + case nsMsgFilterAction::FetchBodyFromPop3Server: + { + uint32_t flags = 0; + nsCOMPtr <nsIMsgFolder> downloadFolder; + msgHdr->GetFolder(getter_AddRefs(downloadFolder)); + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(downloadFolder); + msgHdr->GetFlags(&flags); + if (localFolder && (flags & nsMsgMessageFlags::Partial)) + { + nsCOMPtr<nsIMutableArray> messages = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + messages->AppendElement(msgHdr, false); + localFolder->MarkMsgsOnPop3Server(messages, POP3_FETCH_BODY); + // Don't add this header to the DB, we're going to replace it + // with the full message. + m_msgMovedByFilter = true; + msgIsNew = false; + // Don't do anything else in this filter, wait until we + // have the full message. + *applyMore = false; + } + } + break; + + case nsMsgFilterAction::StopExecution: + { + // don't apply any more filters + *applyMore = false; + } + break; + + case nsMsgFilterAction::Custom: + { + nsCOMPtr<nsIMsgFilterCustomAction> customAction; + rv = filterAction->GetCustomAction(getter_AddRefs(customAction)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString value; + filterAction->GetStrValue(value); + + nsCOMPtr<nsIMutableArray> messageArray( + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_TRUE(messageArray, rv); + if (NS_SUCCEEDED(rv)) + rv = messageArray->AppendElement(msgHdr, false); + + + if (NS_SUCCEEDED(rv)) + rv = customAction->Apply(messageArray, value, nullptr, + nsMsgFilterType::InboxRule, msgWindow); + if (NS_FAILED(rv)) { + // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands. + if (loggingEnabled) + (void) filter->LogRuleHitFail(filterAction, msgHdr, rv, "Copy failed"); + } + } + break; + + + default: + // XXX should not be reached. Check in debug build. + NS_ERROR("This default should not be reached."); + break; + } + } + } + if (!msgIsNew) + { + int32_t numNewMessages; + m_downloadFolder->GetNumNewMessages(false, &numNewMessages); + if (numNewMessages > 0) + m_downloadFolder->SetNumNewMessages(numNewMessages - 1); + m_numNotNewMessages++; + } + return rv; +} + +// this gets run in a second pass, after apply filters to a header. +nsresult nsParseNewMailState::ApplyForwardAndReplyFilter(nsIMsgWindow *msgWindow) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgIncomingServer> server; + + uint32_t i; + uint32_t count = m_forwardTo.Length(); + for (i = 0; i < count; i++) + { + if (!m_forwardTo[i].IsEmpty()) + { + nsAutoString forwardStr; + CopyASCIItoUTF16(m_forwardTo[i], forwardStr); + rv = m_rootFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + { + nsCOMPtr<nsIMsgComposeService> compService = + do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = compService->ForwardMessage(forwardStr, m_msgToForwardOrReply, + msgWindow, server, + nsIMsgComposeService::kForwardAsDefault); + } + } + } + m_forwardTo.Clear(); + + count = m_replyTemplateUri.Length(); + for (i = 0; i < count; i++) + { + if (!m_replyTemplateUri[i].IsEmpty()) + { + // copy this and truncate the original, so we don't accidentally re-use it on the next hdr. + rv = m_rootFolder->GetServer(getter_AddRefs(server)); + if (server) + { + nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ; + if (compService) { + rv = compService->ReplyWithTemplate(m_msgToForwardOrReply, + m_replyTemplateUri[i].get(), + msgWindow, server); + if (NS_FAILED(rv)) { + NS_WARNING("ReplyWithTemplate failed"); + if (rv == NS_ERROR_ABORT) { + m_filter->LogRuleHitFail(m_ruleAction, m_msgToForwardOrReply, rv, + "Sending reply aborted"); + } else { + m_filter->LogRuleHitFail(m_ruleAction, m_msgToForwardOrReply, rv, + "Error sending reply"); + } + } + } + } + } + } + m_replyTemplateUri.Clear(); + m_msgToForwardOrReply = nullptr; + return rv; +} + +void nsParseNewMailState::MarkFilteredMessageRead(nsIMsgDBHdr *msgHdr) +{ + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->MarkMessagesRead(messageArray, true); +} + +void nsParseNewMailState::MarkFilteredMessageUnread(nsIMsgDBHdr *msgHdr) +{ + uint32_t newFlags; + if (m_mailDB) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + m_mailDB->AddToNewList(msgKey); + } + else + { + msgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + } + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + messageArray->AppendElement(msgHdr, false); + m_downloadFolder->MarkMessagesRead(messageArray, false); +} + +nsresult nsParseNewMailState::EndMsgDownload() +{ + if (m_moveCoalescer) + m_moveCoalescer->PlaybackMoves(); + + // need to do this for all folders that had messages filtered into them + uint32_t serverCount = m_filterTargetFolders.Count(); + nsresult rv; + nsCOMPtr<nsIMsgMailSession> session = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && session) // don't use NS_ENSURE_SUCCESS here - we need to release semaphore below + { + for (uint32_t index = 0; index < serverCount; index++) + { + bool folderOpen; + session->IsFolderOpenInWindow(m_filterTargetFolders[index], &folderOpen); + if (!folderOpen) + { + uint32_t folderFlags; + m_filterTargetFolders[index]->GetFlags(&folderFlags); + if (! (folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) + { + bool filtersRun; + m_filterTargetFolders[index]->CallFilterPlugins(nullptr, &filtersRun); + if (!filtersRun) + m_filterTargetFolders[index]->SetMsgDatabase(nullptr); + } + } + } + } + m_filterTargetFolders.Clear(); + return rv; +} + +nsresult nsParseNewMailState::AppendMsgFromStream(nsIInputStream *fileStream, + nsIMsgDBHdr *aHdr, + uint32_t length, + nsIMsgFolder *destFolder) +{ + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(fileStream); + nsCOMPtr<nsIMsgPluggableStore> store; + nsCOMPtr<nsIOutputStream> destOutputStream; + nsresult rv = destFolder->GetMsgStore(getter_AddRefs(store)); + NS_ENSURE_SUCCESS(rv, rv); + bool reusable; + rv = store->GetNewMsgOutputStream(destFolder, &aHdr, &reusable, + getter_AddRefs(destOutputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_ibuffer) + { + m_ibuffer_size = FILE_IO_BUFFER_SIZE; + m_ibuffer = (char *) PR_Malloc(m_ibuffer_size); + NS_ASSERTION(m_ibuffer != nullptr, "couldn't get memory to move msg"); + } + m_ibuffer_fp = 0; + + while (length > 0 && m_ibuffer) + { + uint32_t nRead; + fileStream->Read (m_ibuffer, length > m_ibuffer_size ? m_ibuffer_size : length, &nRead); + if (nRead == 0) + break; + + uint32_t bytesWritten; + // Check the number of bytes actually written to the stream. + destOutputStream->Write(m_ibuffer, nRead, &bytesWritten); + if (bytesWritten != nRead) + { + destOutputStream->Close(); + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + length -= nRead; + } + + NS_ASSERTION(length == 0, "didn't read all of original message in filter move"); + + // non-reusable streams will get closed by the store. + if (reusable) + destOutputStream->Close(); + return store->FinishNewMessage(destOutputStream, aHdr); +} + +/* + * Moves message pointed to by mailHdr into folder destIFolder. + * After successful move mailHdr is no longer usable by the caller. + */ +nsresult nsParseNewMailState::MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr, + nsIMsgDatabase *sourceDB, + nsIMsgFolder *destIFolder, + nsIMsgFilter *filter, + nsIMsgWindow *msgWindow) +{ + NS_ENSURE_ARG_POINTER(destIFolder); + nsresult rv = NS_OK; + + // check if the destination is a real folder (by checking for null parent) + // and if it can file messages (e.g., servers or news folders can't file messages). + // Or read only imap folders... + bool canFileMessages = true; + nsCOMPtr<nsIMsgFolder> parentFolder; + destIFolder->GetParent(getter_AddRefs(parentFolder)); + if (parentFolder) + destIFolder->GetCanFileMessages(&canFileMessages); + if (!parentFolder || !canFileMessages) + { + if (filter) + { + filter->SetEnabled(false); + // we need to explicitly save the filter file. + if (m_filterList) + m_filterList->SaveToDefaultFile(); + destIFolder->ThrowAlertMsg("filterDisabled", msgWindow); + } + return NS_MSG_NOT_A_MAIL_FOLDER; + } + + uint32_t messageLength; + mailHdr->GetMessageSize(&messageLength); + + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(destIFolder); + if (localFolder) { + bool destFolderTooBig = true; + rv = localFolder->WarnIfLocalFileTooBig(msgWindow, messageLength, + &destFolderTooBig); + if (NS_FAILED(rv) || destFolderTooBig) + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + nsCOMPtr<nsISupports> myISupports = + do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this)); + + // Make sure no one else is writing into this folder + if (NS_FAILED(rv = destIFolder->AcquireSemaphore (myISupports))) + { + destIFolder->ThrowAlertMsg("filterFolderDeniedLocked", msgWindow); + return rv; + } + nsCOMPtr<nsIInputStream> inputStream; + bool reusable; + rv = m_downloadFolder->GetMsgInputStream(mailHdr, &reusable, getter_AddRefs(inputStream)); + if (!inputStream) + { + NS_ERROR("couldn't get source msg input stream in move filter"); + destIFolder->ReleaseSemaphore (myISupports); + return NS_MSG_FOLDER_UNREADABLE; // ### dmb + } + + nsCOMPtr<nsIMsgDatabase> destMailDB; + + if (!localFolder) + return NS_MSG_POP_FILTER_TARGET_ERROR; + + // don't force upgrade in place - open the db here before we start writing to the + // destination file because XP_Stat can return file size including bytes written... + rv = localFolder->GetDatabaseWOReparse(getter_AddRefs(destMailDB)); + NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv), + "failed to open mail db parsing folder"); + nsCOMPtr<nsIMsgDBHdr> newHdr; + + if (destMailDB) + rv = destMailDB->CopyHdrFromExistingHdr(m_new_key, mailHdr, true, + getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && !newHdr) + rv = NS_ERROR_UNEXPECTED; + + if (NS_FAILED(rv)) + { + destIFolder->ThrowAlertMsg("filterFolderHdrAddFailed", msgWindow); + } else { + rv = AppendMsgFromStream(inputStream, newHdr, messageLength, destIFolder); + if (NS_FAILED(rv)) + destIFolder->ThrowAlertMsg("filterFolderWriteFailed", msgWindow); + } + + if (NS_FAILED(rv)) + { + if (destMailDB) + destMailDB->Close(true); + + destIFolder->ReleaseSemaphore(myISupports); + + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + bool movedMsgIsNew = false; + // if we have made it this far then the message has successfully been written to the new folder + // now add the header to the destMailDB. + + uint32_t newFlags; + newHdr->GetFlags(&newFlags); + nsMsgKey msgKey; + newHdr->GetMessageKey(&msgKey); + if (!(newFlags & nsMsgMessageFlags::Read)) + { + nsCString junkScoreStr; + (void) newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_HAM_SCORE) + { + newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags); + destMailDB->AddToNewList(msgKey); + movedMsgIsNew = true; + } + } + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgAdded(newHdr); + // mark the header as not yet reported classified + destIFolder->OrProcessingFlags( + msgKey, nsMsgProcessingFlags::NotReportedClassified); + m_msgToForwardOrReply = newHdr; + + if (movedMsgIsNew) + destIFolder->SetHasNewMessages(true); + if (!m_filterTargetFolders.Contains(destIFolder)) + m_filterTargetFolders.AppendObject(destIFolder); + + destIFolder->ReleaseSemaphore (myISupports); + + (void) localFolder->RefreshSizeOnDisk(); + + // Notify the message was moved. + if (notifier) { + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = mailHdr->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv)) { + notifier->NotifyItemEvent(folder, + NS_LITERAL_CSTRING("UnincorporatedMessageMoved"), + newHdr); + } else { + NS_WARNING("Can't get folder for message that was moved."); + } + } + + nsCOMPtr<nsIMsgPluggableStore> store; + rv = m_downloadFolder->GetMsgStore(getter_AddRefs(store)); + if (store) + store->DiscardNewMessage(m_outputStream, mailHdr); + if (sourceDB) + sourceDB->RemoveHeaderMdbRow(mailHdr); + + // update the folder size so we won't reparse. + UpdateDBFolderInfo(destMailDB); + destIFolder->UpdateSummaryTotals(true); + + destMailDB->Commit(nsMsgDBCommitType::kLargeCommit); + return rv; +} |