summaryrefslogtreecommitdiffstats
path: root/mailnews/base/search/src/nsMsgFilter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/search/src/nsMsgFilter.cpp')
-rw-r--r--mailnews/base/search/src/nsMsgFilter.cpp1057
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