diff options
Diffstat (limited to 'mailnews/base/search/src/nsMsgFilter.cpp')
-rw-r--r-- | mailnews/base/search/src/nsMsgFilter.cpp | 1057 |
1 files changed, 1057 insertions, 0 deletions
diff --git a/mailnews/base/search/src/nsMsgFilter.cpp b/mailnews/base/search/src/nsMsgFilter.cpp new file mode 100644 index 000000000..e94240f29 --- /dev/null +++ b/mailnews/base/search/src/nsMsgFilter.cpp @@ -0,0 +1,1057 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// this file implements the nsMsgFilter interface + +#include "msgCore.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgHdr.h" +#include "nsMsgFilterList.h" // for kFileVersion +#include "nsMsgFilter.h" +#include "nsMsgUtils.h" +#include "nsMsgLocalSearch.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgSearchValue.h" +#include "nsMsgI18N.h" +#include "nsIOutputStream.h" +#include "nsIStringBundle.h" +#include "nsDateTimeFormatCID.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIMsgFilterService.h" +#include "nsIMutableArray.h" +#include "prmem.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Services.h" + +static const char *kImapPrefix = "//imap:"; +static const char *kWhitespace = "\b\t\r\n "; + +nsMsgRuleAction::nsMsgRuleAction() +{ +} + +nsMsgRuleAction::~nsMsgRuleAction() +{ +} + +NS_IMPL_ISUPPORTS(nsMsgRuleAction, nsIMsgRuleAction) + +NS_IMPL_GETSET(nsMsgRuleAction, Type, nsMsgRuleActionType, m_type) + +NS_IMETHODIMP nsMsgRuleAction::SetPriority(nsMsgPriorityValue aPriority) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority, + NS_ERROR_ILLEGAL_VALUE); + m_priority = aPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetPriority(nsMsgPriorityValue *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority, + NS_ERROR_ILLEGAL_VALUE); + *aResult = m_priority; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetLabel(nsMsgLabelValue aLabel) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label, + NS_ERROR_ILLEGAL_VALUE); + m_label = aLabel; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetLabel(nsMsgLabelValue *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label, NS_ERROR_ILLEGAL_VALUE); + *aResult = m_label; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetTargetFolderUri(const nsACString &aUri) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder || + m_type == nsMsgFilterAction::CopyToFolder, + NS_ERROR_ILLEGAL_VALUE); + m_folderUri = aUri; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetTargetFolderUri(nsACString &aResult) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder || + m_type == nsMsgFilterAction::CopyToFolder, + NS_ERROR_ILLEGAL_VALUE); + aResult = m_folderUri; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetJunkScore(int32_t aJunkScore) +{ + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore && aJunkScore >= 0 && aJunkScore <= 100, + NS_ERROR_ILLEGAL_VALUE); + m_junkScore = aJunkScore; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetJunkScore(int32_t *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore, NS_ERROR_ILLEGAL_VALUE); + *aResult = m_junkScore; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetStrValue(const nsACString &aStrValue) +{ + m_strValue = aStrValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetStrValue(nsACString &aStrValue) +{ + aStrValue = m_strValue; + return NS_OK; +} + +/* attribute ACString customId; */ +NS_IMETHODIMP nsMsgRuleAction::GetCustomId(nsACString & aCustomId) +{ + aCustomId = m_customId; + return NS_OK; +} + +NS_IMETHODIMP nsMsgRuleAction::SetCustomId(const nsACString & aCustomId) +{ + m_customId = aCustomId; + return NS_OK; +} + +// this can only be called after the customId is set +NS_IMETHODIMP nsMsgRuleAction::GetCustomAction(nsIMsgFilterCustomAction **aCustomAction) +{ + NS_ENSURE_ARG_POINTER(aCustomAction); + if (!m_customAction) + { + if (m_customId.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + nsresult rv; + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filterService->GetCustomAction(m_customId, getter_AddRefs(m_customAction)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // found the correct custom action + NS_ADDREF(*aCustomAction = m_customAction); + return NS_OK; +} + +nsMsgFilter::nsMsgFilter(): + m_temporary(false), + m_unparseable(false), + m_filterList(nullptr), + m_expressionTree(nullptr) +{ + nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList)); + if (NS_FAILED(rv)) + NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter"); + + m_type = nsMsgFilterType::InboxRule | nsMsgFilterType::Manual; +} + +nsMsgFilter::~nsMsgFilter() +{ + delete m_expressionTree; +} + +NS_IMPL_ISUPPORTS(nsMsgFilter, nsIMsgFilter) + +NS_IMPL_GETSET(nsMsgFilter, FilterType, nsMsgFilterTypeType, m_type) +NS_IMPL_GETSET(nsMsgFilter, Enabled, bool, m_enabled) +NS_IMPL_GETSET(nsMsgFilter, Temporary, bool, m_temporary) +NS_IMPL_GETSET(nsMsgFilter, Unparseable, bool, m_unparseable) + +NS_IMETHODIMP nsMsgFilter::GetFilterName(nsAString &name) +{ + name = m_filterName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetFilterName(const nsAString &name) +{ + m_filterName.Assign(name); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetFilterDesc(nsACString &description) +{ + description = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetFilterDesc(const nsACString &description) +{ + m_description.Assign(description); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetUnparsedBuffer(nsACString &unparsedBuffer) +{ + unparsedBuffer = m_unparsedBuffer; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetUnparsedBuffer(const nsACString &unparsedBuffer) +{ + m_unparsedBuffer.Assign(unparsedBuffer); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::AddTerm( + nsMsgSearchAttribValue attrib, /* attribute for this term */ + nsMsgSearchOpValue op, /* operator e.g. opContains */ + nsIMsgSearchValue *value, /* value e.g. "Dogbert" */ + bool BooleanAND, /* true if AND is the boolean operator. + false if OR is the boolean operators */ + const nsACString & arbitraryHeader) /* arbitrary header specified by user. + ignored unless attrib = attribOtherHeader */ +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::AppendTerm(nsIMsgSearchTerm * aTerm) +{ + NS_ENSURE_TRUE(aTerm, NS_ERROR_NULL_POINTER); + // invalidate expression tree if we're changing the terms + delete m_expressionTree; + m_expressionTree = nullptr; + return m_termList->AppendElement(static_cast<nsISupports*>(aTerm)); +} + +NS_IMETHODIMP +nsMsgFilter::CreateTerm(nsIMsgSearchTerm **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsMsgSearchTerm *term = new nsMsgSearchTerm; + NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY); + + *aResult = static_cast<nsIMsgSearchTerm*>(term); + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::CreateAction(nsIMsgRuleAction **aAction) +{ + NS_ENSURE_ARG_POINTER(aAction); + nsMsgRuleAction *action = new nsMsgRuleAction; + NS_ENSURE_TRUE(action, NS_ERROR_OUT_OF_MEMORY); + + *aAction = static_cast<nsIMsgRuleAction*>(action); + NS_ADDREF(*aAction); + return NS_OK; +} + +// All the rules' actions form a unit, with no real order imposed. +// But certain actions like MoveToFolder or StopExecution would make us drop +// consecutive actions, while actions like AddTag implicitly care about the +// order of invocation. Hence we do as little reordering as possible, keeping +// the user-defined order as much as possible. +// We explicitly don't allow for filters which do "tag message as Important, +// copy it to another folder, tag it as To Do also, copy this different state +// elsewhere" in one go. You need to define separate filters for that. +// +// The order of actions returned by this method: +// index action(s) +// ------- --------- +// 0 FetchBodyFromPop3Server +// 1..n all other 'normal' actions, in their original order +// n+1..m CopyToFolder +// m+1 MoveToFolder or Delete +// m+2 StopExecution +NS_IMETHODIMP +nsMsgFilter::GetSortedActionList(nsIArray **aActionList) +{ + NS_ENSURE_ARG_POINTER(aActionList); + + uint32_t numActions; + nsresult rv = GetActionCount(&numActions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> orderedActions(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // hold separate pointers into the action list + uint32_t nextIndexForNormal = 0, nextIndexForCopy = 0, nextIndexForMove = 0; + for (uint32_t index = 0; index < numActions; ++index) + { + nsCOMPtr<nsIMsgRuleAction> action; + rv = GetActionAt(index, getter_AddRefs(action)); + if (NS_FAILED(rv) || !action) + continue; + + nsMsgRuleActionType actionType; + action->GetType(&actionType); + switch (actionType) + { + case nsMsgFilterAction::FetchBodyFromPop3Server: + { + // always insert in front + rv = orderedActions->InsertElementAt(action, 0, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForNormal; + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::CopyToFolder: + { + // insert into copy actions block, in order of appearance + rv = orderedActions->InsertElementAt(action, nextIndexForCopy, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::MoveToFolder: + case nsMsgFilterAction::Delete: + { + // insert into move/delete action block + rv = orderedActions->InsertElementAt(action, nextIndexForMove, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::StopExecution: + { + // insert into stop action block + rv = orderedActions->AppendElement(action, false); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + + default: + { + // insert into normal action block, in order of appearance + rv = orderedActions->InsertElementAt(action, nextIndexForNormal, false); + NS_ENSURE_SUCCESS(rv, rv); + ++nextIndexForNormal; + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + } + } + + orderedActions.forget(aActionList); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::AppendAction(nsIMsgRuleAction *aAction) +{ + NS_ENSURE_ARG_POINTER(aAction); + + m_actionList.AppendElement(aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionAt(uint32_t aIndex, nsIMsgRuleAction **aAction) +{ + NS_ENSURE_ARG_POINTER(aAction); + NS_ENSURE_ARG(aIndex < m_actionList.Length()); + + NS_ENSURE_TRUE(*aAction = m_actionList[aIndex], NS_ERROR_ILLEGAL_VALUE); + NS_IF_ADDREF(*aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionIndex(nsIMsgRuleAction *aAction, int32_t *aIndex) +{ + NS_ENSURE_ARG_POINTER(aIndex); + + *aIndex = m_actionList.IndexOf(aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionCount(uint32_t *aCount) +{ + NS_ENSURE_ARG_POINTER(aCount); + + *aCount = m_actionList.Length(); + return NS_OK; +} + +NS_IMETHODIMP //for editing a filter +nsMsgFilter::ClearActionList() +{ + m_actionList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetTerm(int32_t termIndex, + nsMsgSearchAttribValue *attrib, /* attribute for this term */ + nsMsgSearchOpValue *op, /* operator e.g. opContains */ + nsIMsgSearchValue **value, /* value e.g. "Dogbert" */ + bool *booleanAnd, /* true if AND is the boolean operator. false if OR is the boolean operator */ + nsACString &arbitraryHeader) /* arbitrary header specified by user.ignore unless attrib = attribOtherHeader */ +{ + nsCOMPtr<nsIMsgSearchTerm> term; + nsresult rv = m_termList->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(term)); + if (NS_SUCCEEDED(rv) && term) + { + if (attrib) + term->GetAttrib(attrib); + if (op) + term->GetOp(op); + if (value) + term->GetValue(value); + if (booleanAnd) + term->GetBooleanAnd(booleanAnd); + if (attrib && *attrib > nsMsgSearchAttrib::OtherHeader + && *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) + term->GetArbitraryHeader(arbitraryHeader); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetSearchTerms(nsISupportsArray **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + // caller can change m_termList, which can invalidate m_expressionTree. + delete m_expressionTree; + m_expressionTree = nullptr; + NS_IF_ADDREF(*aResult = m_termList); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetSearchTerms(nsISupportsArray *aSearchList) +{ + delete m_expressionTree; + m_expressionTree = nullptr; + m_termList = aSearchList; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetScope(nsIMsgSearchScopeTerm *aResult) +{ + m_scope = aResult; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetScope(nsIMsgSearchScopeTerm **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = m_scope); + return NS_OK; +} + +#define LOG_ENTRY_START_TAG "<p>\n" +#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG)) +#define LOG_ENTRY_END_TAG "</p>\n" +#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG)) +// Does this need to be localizable? +#define LOG_ENTRY_TIMESTAMP "[$S] " + +// This function handles the logging both for success of filtering +// (NS_SUCCEEDED(aRcode)), and for error reporting (NS_FAILED(aRcode) +// when the filter action (such as file move/copy) failed. +// +// @param aRcode NS_OK for successful filtering +// operation, otherwise, an error code for filtering failure. +// @param aErrmsg Not used for success case (ignored), and a non-null +// error message for failure case. +// +// CAUTION: Unless logging is enabled, no error/warning is shown. +// So enable logging if you would like to see the error/warning. +// +// XXX The current code in this file does not report errors of minor +// operations such as adding labels and so forth which may fail when +// underlying file system for the message store experiences +// failure. For now, most visible major errors such as message +// move/copy failures are taken care of. +// +// XXX Possible Improvement: For error case reporting, someone might +// want to implement a transient message that appears and stick until +// the user clears in the message status bar, etc. For now, we log an +// error in a similar form as a conventional successful filter event +// with additional error information at the beginning. +// +nsresult +nsMsgFilter::LogRuleHitGeneric(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr, + nsresult aRcode, + const char *aErrmsg) +{ + NS_ENSURE_ARG_POINTER(aFilterAction); + NS_ENSURE_ARG_POINTER(aMsgHdr); + + NS_ENSURE_TRUE(m_filterList, NS_OK); + nsCOMPtr <nsIOutputStream> logStream; + nsresult rv = m_filterList->GetLogStream(getter_AddRefs(logStream)); + NS_ENSURE_SUCCESS(rv,rv); + + PRTime date; + nsMsgRuleActionType actionType; + + nsString authorValue; + nsString subjectValue; + nsString filterName; + nsString dateValue; + + GetFilterName(filterName); + aFilterAction->GetType(&actionType); + (void)aMsgHdr->GetDate(&date); + PRExplodedTime exploded; + PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded); + + if (!mDateFormatter) + { + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!mDateFormatter) + { + return NS_ERROR_FAILURE; + } + } + mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort, + kTimeFormatSeconds, &exploded, + dateValue); + + (void)aMsgHdr->GetMime2DecodedAuthor(authorValue); + (void)aMsgHdr->GetMime2DecodedSubject(subjectValue); + + nsCString buffer; +#ifdef MOZILLA_INTERNAL_API + // this is big enough to hold a log entry. + // do this so we avoid growing and copying as we append to the log. + buffer.SetCapacity(512); +#endif + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + // If error, prefix with the error code and error message. + // A desired wording (without NEWLINEs): + // Filter Action Failed "Move failed" with error code=0x80004005 + // while attempting: Applied filter "test" to message from + // Some Test <test@example.com> - send test 3 at 2/13/2015 11:32:53 AM + // moved message id = 54DE5165.7000907@example.com to + // mailbox://nobody@Local%20Folders/test + + if (NS_FAILED(aRcode)) + { + + // Let us put "Filter Action Failed: "%s" with error code=%s while attempting: " inside bundle. + // Convert aErrmsg to UTF16 string, and + // convert aRcode to UTF16 string in advance. + + char tcode[20]; + PR_snprintf(tcode, sizeof(tcode), "0x%08x", aRcode); + + NS_ConvertASCIItoUTF16 tcode16(tcode) ; + NS_ConvertASCIItoUTF16 tErrmsg16(aErrmsg) ; + + const char16_t *logErrorFormatStrings[2] = { tErrmsg16.get(), tcode16.get()}; + nsString filterFailureWarningPrefix; + rv = bundle->FormatStringFromName( + u"filterFailureWarningPrefix", + logErrorFormatStrings, 2, + getter_Copies(filterFailureWarningPrefix)); + NS_ENSURE_SUCCESS(rv, rv); + buffer += NS_ConvertUTF16toUTF8(filterFailureWarningPrefix); + buffer += "\n"; + } + + const char16_t *filterLogDetectFormatStrings[4] = { filterName.get(), authorValue.get(), subjectValue.get(), dateValue.get() }; + nsString filterLogDetectStr; + rv = bundle->FormatStringFromName( + u"filterLogDetectStr", + filterLogDetectFormatStrings, 4, + getter_Copies(filterLogDetectStr)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(filterLogDetectStr); + buffer += "\n"; + + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) + { + nsCString actionFolderUri; + aFilterAction->GetTargetFolderUri(actionFolderUri); + NS_ConvertASCIItoUTF16 actionFolderUriValue(actionFolderUri); + + nsCString msgId; + aMsgHdr->GetMessageId(getter_Copies(msgId)); + NS_ConvertASCIItoUTF16 msgIdValue(msgId); + + const char16_t *logMoveFormatStrings[2] = { msgIdValue.get(), actionFolderUriValue.get() }; + nsString logMoveStr; + rv = bundle->FormatStringFromName( + (actionType == nsMsgFilterAction::MoveToFolder) ? + u"logMoveStr" : u"logCopyStr", + logMoveFormatStrings, 2, + getter_Copies(logMoveStr)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(logMoveStr); + } + else if (actionType == nsMsgFilterAction::Custom) + { + nsCOMPtr<nsIMsgFilterCustomAction> customAction; + nsAutoString filterActionName; + rv = aFilterAction->GetCustomAction(getter_AddRefs(customAction)); + if (NS_SUCCEEDED(rv) && customAction) + customAction->GetName(filterActionName); + if (filterActionName.IsEmpty()) + bundle->GetStringFromName( + u"filterMissingCustomAction", + getter_Copies(filterActionName)); + buffer += NS_ConvertUTF16toUTF8(filterActionName); + } + else + { + nsString actionValue; + nsAutoString filterActionID; + filterActionID = NS_LITERAL_STRING("filterAction"); + filterActionID.AppendInt(actionType); + rv = bundle->GetStringFromName(filterActionID.get(), getter_Copies(actionValue)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(actionValue); + } + buffer += "\n"; + + // Prepare timestamp + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort, + kTimeFormatSeconds, &exploded, + dateValue); + + nsCString timestampString(LOG_ENTRY_TIMESTAMP); + MsgReplaceSubstring(timestampString, "$S", NS_ConvertUTF16toUTF8(dateValue).get()); + + // XXX: Finally, here we have enough context and buffer + // (string) to display the filtering error if we want: for + // example, a sticky error message in status bar, etc. + + uint32_t writeCount; + + rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN, "failed to write out start log tag"); + + rv = logStream->Write(timestampString.get(), timestampString.Length(), &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == timestampString.Length(), "failed to write out timestamp"); + + // HTML-escape the log for security reasons. + // We don't want someone to send us a message with a subject with + // HTML tags, especially <script>. + char *escapedBuffer = MsgEscapeHTML(buffer.get()); + if (!escapedBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t escapedBufferLen = strlen(escapedBuffer); + rv = logStream->Write(escapedBuffer, escapedBufferLen, &writeCount); + PR_Free(escapedBuffer); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit"); + + rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN, "failed to write out end log tag"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::LogRuleHit(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr) +{ + return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, NS_OK, nullptr); +} + +NS_IMETHODIMP nsMsgFilter::LogRuleHitFail(nsIMsgRuleAction *aFilterAction, + nsIMsgDBHdr *aMsgHdr, + nsresult aRcode, + const char *aErrMsg) +{ + return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, aRcode, aErrMsg); +} + +NS_IMETHODIMP +nsMsgFilter::MatchHdr(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder, + nsIMsgDatabase *db, const char *headers, + uint32_t headersSize, bool *pResult) +{ + NS_ENSURE_ARG_POINTER(folder); + NS_ENSURE_ARG_POINTER(msgHdr); + // use offlineMail because + nsCString folderCharset; + folder->GetCharset(folderCharset); + nsresult rv = nsMsgSearchOfflineMail::MatchTermsForFilter(msgHdr, m_termList, + folderCharset.get(), m_scope, db, headers, headersSize, &m_expressionTree, pResult); + return rv; +} + +NS_IMETHODIMP +nsMsgFilter::SetFilterList(nsIMsgFilterList *filterList) +{ + // doesn't hold a ref. + m_filterList = filterList; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetFilterList(nsIMsgFilterList **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = m_filterList); + return NS_OK; +} + +void nsMsgFilter::SetFilterScript(nsCString *fileName) +{ + m_scriptFileName = *fileName; +} + +nsresult nsMsgFilter::ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction *filterAction, nsCString &moveValue) +{ + NS_ENSURE_ARG_POINTER(filterAction); + int16_t filterVersion = kFileVersion; + if (m_filterList) + m_filterList->GetVersion(&filterVersion); + if (filterVersion <= k60Beta1Version) + { + nsCOMPtr <nsIMsgFolder> rootFolder; + nsCString folderUri; + + m_filterList->GetFolder(getter_AddRefs(rootFolder)); + // if relative path starts with kImap, this is a move to folder on the same server + if (moveValue.Find(kImapPrefix) == 0) + { + int32_t prefixLen = PL_strlen(kImapPrefix); + nsAutoCString originalServerPath(Substring(moveValue, prefixLen)); + if (filterVersion == k45Version) + { + nsAutoString unicodeStr; + nsresult rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), + originalServerPath, + unicodeStr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CopyUTF16toMUTF7(unicodeStr, originalServerPath); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr <nsIMsgFolder> destIFolder; + if (rootFolder) + { + rootFolder->FindSubFolder(originalServerPath, getter_AddRefs(destIFolder)); + if (destIFolder) + { + destIFolder->GetURI(folderUri); + filterAction->SetTargetFolderUri(folderUri); + moveValue.Assign(folderUri); + } + } + } + else + { + // start off leaving the value the same. + filterAction->SetTargetFolderUri(moveValue); + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgFolder> localMailRoot; + rootFolder->GetURI(folderUri); + // if the root folder is not imap, than the local mail root is the server root. + // otherwise, it's the migrated local folders. + if (!StringBeginsWith(folderUri, NS_LITERAL_CSTRING("imap:"))) + localMailRoot = rootFolder; + else + { + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgIncomingServer> server; + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + rv = server->GetRootFolder(getter_AddRefs(localMailRoot)); + } + if (NS_SUCCEEDED(rv) && localMailRoot) + { + nsCString localRootURI; + nsCOMPtr <nsIMsgFolder> destIMsgFolder; + nsCOMPtr <nsIMsgFolder> localMailRootMsgFolder = do_QueryInterface(localMailRoot); + localMailRoot->GetURI(localRootURI); + nsCString destFolderUri; + destFolderUri.Assign( localRootURI); + // need to remove ".sbd" from moveValue, and perhaps escape it. + int32_t offset = moveValue.Find(".sbd/"); + if (offset != -1) + moveValue.Cut(offset, 4); + +#ifdef XP_MACOSX + nsCString unescapedMoveValue; + MsgUnescapeString(moveValue, 0, unescapedMoveValue); + moveValue = unescapedMoveValue; +#endif + destFolderUri.Append('/'); + if (filterVersion == k45Version) + { + nsAutoString unicodeStr; + rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), + moveValue, unicodeStr); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_MsgEscapeEncodeURLPath(unicodeStr, moveValue); + } + destFolderUri.Append(moveValue); + localMailRootMsgFolder->GetChildWithURI (destFolderUri, true, false /*caseInsensitive*/, getter_AddRefs(destIMsgFolder)); + + if (destIMsgFolder) + { + destIMsgFolder->GetURI(folderUri); + filterAction->SetTargetFolderUri(folderUri); + moveValue.Assign(folderUri); + } + } + } + } + else + filterAction->SetTargetFolderUri(moveValue); + + return NS_OK; + // set m_action.m_value.m_folderUri +} + +NS_IMETHODIMP +nsMsgFilter::SaveToTextFile(nsIOutputStream *aStream) +{ + NS_ENSURE_ARG_POINTER(aStream); + if (m_unparseable) + { + uint32_t bytesWritten; + //we need to trim leading whitespaces before filing out + m_unparsedBuffer.Trim(kWhitespace, true /*leadingCharacters*/, false /*trailingCharacters*/); + return aStream->Write(m_unparsedBuffer.get(), m_unparsedBuffer.Length(), &bytesWritten); + } + nsresult err = m_filterList->WriteWstrAttr(nsIMsgFilterList::attribName, m_filterName.get(), aStream); + err = m_filterList->WriteBoolAttr(nsIMsgFilterList::attribEnabled, m_enabled, aStream); + err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribDescription, m_description.get(), aStream); + err = m_filterList->WriteIntAttr(nsIMsgFilterList::attribType, m_type, aStream); + if (IsScript()) + err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribScriptFile, m_scriptFileName.get(), aStream); + else + err = SaveRule(aStream); + return err; +} + +nsresult nsMsgFilter::SaveRule(nsIOutputStream *aStream) +{ + nsresult err = NS_OK; + nsCOMPtr<nsIMsgFilterList> filterList; + GetFilterList(getter_AddRefs(filterList)); + nsAutoCString actionFilingStr; + + uint32_t numActions; + err = GetActionCount(&numActions); + NS_ENSURE_SUCCESS(err, err); + + for (uint32_t index = 0; index < numActions; index++) + { + nsCOMPtr<nsIMsgRuleAction> action; + err = GetActionAt(index, getter_AddRefs(action)); + if (NS_FAILED(err) || !action) + continue; + + nsMsgRuleActionType actionType; + action->GetType(&actionType); + GetActionFilingStr(actionType, actionFilingStr); + + err = filterList->WriteStrAttr(nsIMsgFilterList::attribAction, actionFilingStr.get(), aStream); + NS_ENSURE_SUCCESS(err, err); + + switch(actionType) + { + case nsMsgFilterAction::MoveToFolder: + case nsMsgFilterAction::CopyToFolder: + { + nsCString imapTargetString; + action->GetTargetFolderUri(imapTargetString); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, imapTargetString.get(), aStream); + } + break; + case nsMsgFilterAction::ChangePriority: + { + nsMsgPriorityValue priorityValue; + action->GetPriority(&priorityValue); + nsAutoCString priority; + NS_MsgGetUntranslatedPriorityName(priorityValue, priority); + err = filterList->WriteStrAttr( + nsIMsgFilterList::attribActionValue, priority.get(), aStream); + } + break; + case nsMsgFilterAction::Label: + { + nsMsgLabelValue label; + action->GetLabel(&label); + err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, label, aStream); + } + break; + case nsMsgFilterAction::JunkScore: + { + int32_t junkScore; + action->GetJunkScore(&junkScore); + err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, junkScore, aStream); + } + break; + case nsMsgFilterAction::AddTag: + case nsMsgFilterAction::Reply: + case nsMsgFilterAction::Forward: + { + nsCString strValue; + action->GetStrValue(strValue); + // strValue is e-mail address + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, strValue.get(), aStream); + } + break; + case nsMsgFilterAction::Custom: + { + nsAutoCString id; + action->GetCustomId(id); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribCustomId, id.get(), aStream); + nsAutoCString strValue; + action->GetStrValue(strValue); + if (strValue.Length()) + err = filterList->WriteWstrAttr(nsIMsgFilterList::attribActionValue, + NS_ConvertUTF8toUTF16(strValue).get(), + aStream); + } + break; + + default: + break; + } + } + // and here the fun begins - file out term list... + nsAutoCString condition; + err = MsgTermListToString(m_termList, condition); + if (NS_SUCCEEDED(err)) + err = filterList->WriteStrAttr(nsIMsgFilterList::attribCondition, condition.get(), aStream); + return err; +} + +// for each action, this table encodes the filterTypes that support the action. +struct RuleActionsTableEntry +{ + nsMsgRuleActionType action; + const char* actionFilingStr; /* used for filing out filters, don't translate! */ +}; + +static struct RuleActionsTableEntry ruleActionsTable[] = +{ + { nsMsgFilterAction::MoveToFolder, "Move to folder"}, + { nsMsgFilterAction::CopyToFolder, "Copy to folder"}, + { nsMsgFilterAction::ChangePriority, "Change priority"}, + { nsMsgFilterAction::Delete, "Delete"}, + { nsMsgFilterAction::MarkRead, "Mark read"}, + { nsMsgFilterAction::KillThread, "Ignore thread"}, + { nsMsgFilterAction::KillSubthread, "Ignore subthread"}, + { nsMsgFilterAction::WatchThread, "Watch thread"}, + { nsMsgFilterAction::MarkFlagged, "Mark flagged"}, + { nsMsgFilterAction::Label, "Label"}, + { nsMsgFilterAction::Reply, "Reply"}, + { nsMsgFilterAction::Forward, "Forward"}, + { nsMsgFilterAction::StopExecution, "Stop execution"}, + { nsMsgFilterAction::DeleteFromPop3Server, "Delete from Pop3 server"}, + { nsMsgFilterAction::LeaveOnPop3Server, "Leave on Pop3 server"}, + { nsMsgFilterAction::JunkScore, "JunkScore"}, + { nsMsgFilterAction::FetchBodyFromPop3Server, "Fetch body from Pop3Server"}, + { nsMsgFilterAction::AddTag, "AddTag"}, + { nsMsgFilterAction::MarkUnread, "Mark unread"}, + { nsMsgFilterAction::Custom, "Custom"}, +}; + +static const unsigned int sNumActions = MOZ_ARRAY_LENGTH(ruleActionsTable); + +const char *nsMsgFilter::GetActionStr(nsMsgRuleActionType action) +{ + for (unsigned int i = 0; i < sNumActions; i++) + { + if (action == ruleActionsTable[i].action) + return ruleActionsTable[i].actionFilingStr; + } + return ""; +} +/*static */nsresult nsMsgFilter::GetActionFilingStr(nsMsgRuleActionType action, nsCString &actionStr) +{ + for (unsigned int i = 0; i < sNumActions; i++) + { + if (action == ruleActionsTable[i].action) + { + actionStr = ruleActionsTable[i].actionFilingStr; + return NS_OK; + } + } + return NS_ERROR_INVALID_ARG; +} + + +nsMsgRuleActionType nsMsgFilter::GetActionForFilingStr(nsCString &actionStr) +{ + for (unsigned int i = 0; i < sNumActions; i++) + { + if (actionStr.Equals(ruleActionsTable[i].actionFilingStr)) + return ruleActionsTable[i].action; + } + return nsMsgFilterAction::None; +} + +int16_t +nsMsgFilter::GetVersion() +{ + if (!m_filterList) return 0; + int16_t version; + m_filterList->GetVersion(&version); + return version; +} + +#ifdef DEBUG +void nsMsgFilter::Dump() +{ + nsAutoCString s; + LossyCopyUTF16toASCII(m_filterName, s); + printf("filter %s type = %c desc = %s\n", s.get(), m_type + '0', m_description.get()); +} +#endif |