in mailnews/imap/src/nsImapMailFolder.cpp [3265:3638]
NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter* filter,
nsIMsgWindow* msgWindow,
bool* applyMore) {
//
// This routine is called indirectly from ApplyFiltersToHdr in two
// circumstances, controlled by m_filterListRequiresBody:
//
// If false, after headers are parsed in NormalEndHeaderParseStream.
// If true, after the message body is downloaded in NormalEndMsgWriteStream.
//
// In NormalEndHeaderParseStream, the message has not been added to the
// database, and it is important that database notifications and count
// updates do not occur. In NormalEndMsgWriteStream, the message has been
// added to the database, and database notifications and count updates
// should be performed.
//
NS_ENSURE_ARG_POINTER(filter);
NS_ENSURE_ARG_POINTER(applyMore);
nsresult rv = NS_OK;
nsCOMPtr<nsIMsgDBHdr> msgHdr;
if (m_filterListRequiresBody)
GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
else if (m_msgParser)
m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
NS_ENSURE_TRUE(msgHdr,
NS_ERROR_NULL_POINTER); // fatal error, cannot apply filters
bool deleteToTrash = DeleteIsMoveToTrash();
nsTArray<RefPtr<nsIMsgRuleAction>> filterActionList;
rv = filter->GetSortedActionList(filterActionList);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t numActions = filterActionList.Length();
nsCString msgId;
msgHdr->GetMessageId(msgId);
nsMsgKey msgKey;
msgHdr->GetMessageKey(&msgKey);
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Imap) Applying %" PRIu32
" filter actions on message with key %" PRIu32,
numActions, msgKeyToInt(msgKey)));
MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
("(Imap) Message ID: %s", msgId.get()));
bool loggingEnabled = false;
if (m_filterList && numActions)
(void)m_filterList->GetLoggingEnabled(&loggingEnabled);
bool msgIsNew = true;
rv = GetDatabase();
NS_ENSURE_SUCCESS(rv, rv);
nsresult finalResult = NS_OK; // result of all actions
for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) {
nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
if (!filterAction) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
("(Imap) Filter action at index %" PRIu32 " invalid, skipping",
actionIndex));
continue;
}
rv = NS_OK; // result of the current action
nsMsgRuleActionType actionType;
if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Imap) Running filter action at index %" PRIu32
", action type = %i",
actionIndex, actionType));
if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr);
nsCString actionTargetFolderUri;
if (actionType == nsMsgFilterAction::MoveToFolder ||
actionType == nsMsgFilterAction::CopyToFolder) {
rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) {
// clang-format off
MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
("(Imap) Target URI for Copy/Move action is empty, skipping"));
// clang-format on
NS_ASSERTION(false, "actionTargetFolderUri is empty");
continue;
}
}
uint32_t msgFlags;
msgHdr->GetFlags(&msgFlags);
bool isRead = (msgFlags & nsMsgMessageFlags::Read);
switch (actionType) {
case nsMsgFilterAction::Delete: {
if (deleteToTrash) {
// set value to trash folder
nsCOMPtr<nsIMsgFolder> mailTrash;
rv = GetTrashFolder(getter_AddRefs(mailTrash));
if (NS_SUCCEEDED(rv) && mailTrash) {
rv = mailTrash->GetURI(actionTargetFolderUri);
if (NS_FAILED(rv)) break;
}
// msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark
// read in trash.
} else {
mDatabase->MarkRead(msgKey, true, nullptr);
mDatabase->MarkImapDeleted(msgKey, true, nullptr);
rv = StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
{msgKey}, nullptr);
if (NS_FAILED(rv)) break;
// this will prevent us from adding the header to the db.
m_msgMovedByFilter = true;
}
msgIsNew = false;
}
// note that delete falls through to move.
[[fallthrough]];
case nsMsgFilterAction::MoveToFolder: {
// if moving to a different file, do it.
nsCString uri;
rv = GetURI(uri);
if (NS_FAILED(rv)) break;
if (!actionTargetFolderUri.Equals(uri)) {
msgHdr->GetFlags(&msgFlags);
if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
mDatabase->MarkMDNSent(msgKey, true, nullptr);
}
nsresult rv = MoveIncorporatedMessage(
msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
if (NS_SUCCEEDED(rv)) {
m_msgMovedByFilter = true;
} else {
if (loggingEnabled) {
(void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
"filterFailureMoveFailed"_ns);
}
}
}
// don't apply any more filters, even if it was a move to the same
// folder
*applyMore = false;
} break;
case nsMsgFilterAction::CopyToFolder: {
nsCString uri;
rv = GetURI(uri);
if (NS_FAILED(rv)) break;
if (!actionTargetFolderUri.Equals(uri)) {
// XXXshaver I'm not actually 100% what the right semantics are for
// MDNs and copied messages, but I suspect deep down inside that
// we probably want to suppress them only on the copies.
msgHdr->GetFlags(&msgFlags);
if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
mDatabase->MarkMDNSent(msgKey, true, nullptr);
}
nsCOMPtr<nsIMsgFolder> dstFolder;
rv = GetExistingFolder(actionTargetFolderUri,
getter_AddRefs(dstFolder));
if (NS_FAILED(rv)) break;
nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(
"@mozilla.org/messenger/messagecopyservice;1", &rv);
if (NS_FAILED(rv)) break;
rv = copyService->CopyMessages(this, {&*msgHdr}, dstFolder, false,
nullptr, msgWindow, false);
if (NS_FAILED(rv)) {
if (loggingEnabled) {
(void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
"filterFailureCopyFailed"_ns);
}
}
}
} break;
case nsMsgFilterAction::MarkRead: {
mDatabase->MarkRead(msgKey, true, nullptr);
rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
msgIsNew = false;
} break;
case nsMsgFilterAction::MarkUnread: {
mDatabase->MarkRead(msgKey, false, nullptr);
rv = StoreImapFlags(kImapMsgSeenFlag, false, {msgKey}, nullptr);
msgIsNew = true;
} break;
case nsMsgFilterAction::MarkFlagged: {
mDatabase->MarkMarked(msgKey, true, nullptr);
rv = StoreImapFlags(kImapMsgFlaggedFlag, true, {msgKey}, nullptr);
} break;
case nsMsgFilterAction::KillThread:
case nsMsgFilterAction::WatchThread: {
nsCOMPtr<nsIMsgThread> msgThread;
nsMsgKey threadKey;
mDatabase->GetThreadContainingMsgHdr(msgHdr,
getter_AddRefs(msgThread));
if (msgThread) {
msgThread->GetThreadKey(&threadKey);
if (actionType == nsMsgFilterAction::KillThread)
rv = mDatabase->MarkThreadIgnored(msgThread, threadKey, true,
nullptr);
else
rv = mDatabase->MarkThreadWatched(msgThread, threadKey, true,
nullptr);
} else {
if (actionType == nsMsgFilterAction::KillThread)
rv = msgHdr->SetUint32Property("ProtoThreadFlags",
nsMsgMessageFlags::Ignored);
else
rv = msgHdr->SetUint32Property("ProtoThreadFlags",
nsMsgMessageFlags::Watched);
}
if (actionType == nsMsgFilterAction::KillThread) {
mDatabase->MarkRead(msgKey, true, nullptr);
rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
msgIsNew = false;
}
} break;
case nsMsgFilterAction::KillSubthread: {
mDatabase->MarkKilled(msgKey, true, nullptr);
mDatabase->MarkRead(msgKey, true, nullptr);
rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
msgIsNew = false;
} break;
case nsMsgFilterAction::ChangePriority: {
nsMsgPriorityValue filterPriority; // a int32_t
filterAction->GetPriority(&filterPriority);
rv = mDatabase->SetUint32PropertyByHdr(
msgHdr, "priority", static_cast<uint32_t>(filterPriority));
} break;
case nsMsgFilterAction::AddTag: {
nsCString keyword;
filterAction->GetStrValue(keyword);
rv = AddKeywordsToMessages({&*msgHdr}, keyword);
} break;
case nsMsgFilterAction::JunkScore: {
nsAutoCString junkScoreStr;
int32_t junkScore;
filterAction->GetJunkScore(&junkScore);
SetJunkScoreForMessage(msgHdr, junkScore, "filter"_ns, -1);
// If score is available, set up to store junk status on server.
if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE ||
junkScore == nsIJunkMailPlugin::IS_HAM_SCORE) {
nsTArray<nsMsgKey>* keysToClassify = m_moveCoalescer->GetKeyBucket(
(junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1);
NS_ASSERTION(keysToClassify, "error getting key bucket");
if (keysToClassify) keysToClassify->AppendElement(msgKey);
if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) {
msgIsNew = false;
mDatabase->MarkNotNew(msgKey, nullptr);
// nsMsgDBFolder::SendFlagNotifications by the call to
// SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
// only if the message is also read and database notifications
// are active, but we are not going to mark it read in this
// action, preferring to leave the choice to the user.
// So correct numNewMessages.
if (m_filterListRequiresBody) {
msgHdr->GetFlags(&msgFlags);
if (!(msgFlags & nsMsgMessageFlags::Read)) {
int32_t numNewMessages;
GetNumNewMessages(false, &numNewMessages);
SetNumNewMessages(--numNewMessages);
SetHasNewMessages(numNewMessages != 0);
}
}
}
}
} break;
case nsMsgFilterAction::Forward: {
nsCString forwardTo;
filterAction->GetStrValue(forwardTo);
nsCOMPtr<nsIMsgIncomingServer> server;
rv = GetServer(getter_AddRefs(server));
if (NS_FAILED(rv)) break;
if (!forwardTo.IsEmpty()) {
nsCOMPtr<nsIMsgComposeService> compService =
do_GetService("@mozilla.org/messengercompose;1", &rv);
if (NS_FAILED(rv)) break;
rv = compService->ForwardMessage(
NS_ConvertUTF8toUTF16(forwardTo), msgHdr, msgWindow, server,
nsIMsgComposeService::kForwardAsDefault);
}
} break;
case nsMsgFilterAction::Reply: {
nsCString replyTemplateUri;
filterAction->GetStrValue(replyTemplateUri);
nsCOMPtr<nsIMsgIncomingServer> server;
rv = GetServer(getter_AddRefs(server));
if (NS_FAILED(rv)) break;
if (!replyTemplateUri.IsEmpty()) {
nsCOMPtr<nsIMsgComposeService> compService =
do_GetService("@mozilla.org/messengercompose;1", &rv);
if (NS_SUCCEEDED(rv) && compService) {
rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri,
msgWindow, server);
if (NS_FAILED(rv)) {
NS_WARNING("ReplyWithTemplate failed");
if (rv == NS_ERROR_ABORT) {
(void)filter->LogRuleHitFail(
filterAction, msgHdr, rv,
"filterFailureSendingReplyAborted"_ns);
} else {
(void)filter->LogRuleHitFail(
filterAction, msgHdr, rv,
"filterFailureSendingReplyError"_ns);
}
}
}
}
} break;
case nsMsgFilterAction::StopExecution: {
// don't apply any more filters
*applyMore = false;
rv = NS_OK;
} break;
case nsMsgFilterAction::Custom: {
nsCOMPtr<nsIMsgFilterCustomAction> customAction;
rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
if (NS_FAILED(rv)) break;
nsAutoCString value;
rv = filterAction->GetStrValue(value);
if (NS_FAILED(rv)) break;
rv = customAction->ApplyAction({&*msgHdr}, value, nullptr,
nsMsgFilterType::InboxRule, msgWindow);
// allow custom action to affect new
msgHdr->GetFlags(&msgFlags);
if (!(msgFlags & nsMsgMessageFlags::New)) msgIsNew = false;
} break;
default:
NS_ERROR("unexpected filter action");
rv = NS_ERROR_UNEXPECTED;
break;
}
}
if (NS_FAILED(rv)) {
finalResult = rv;
MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
("(Imap) Action execution failed with error: %" PRIx32,
static_cast<uint32_t>(rv)));
if (loggingEnabled) {
(void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
"filterFailureAction"_ns);
}
} else {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Imap) Action execution succeeded"));
}
}
if (!msgIsNew) {
int32_t numNewMessages;
GetNumNewMessages(false, &numNewMessages);
// When database notifications are active, new counts will be reset
// to zero in nsMsgDBFolder::SendFlagNotifications by the call to
// SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
if (!m_filterListRequiresBody) SetNumNewMessages(--numNewMessages);
if (mDatabase) mDatabase->MarkNotNew(msgKey, nullptr);
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Imap) Message will not be marked new"));
}
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Imap) Finished executing actions"));
return finalResult;
}