in mailnews/local/src/nsParseMailbox.cpp [1424:1787]
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;
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,
("(Local) Applying %" PRIu32
" filter actions on message with key %" PRIu32,
numActions, msgKeyToInt(msgKey)));
MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
("(Local) Message ID: %s", msgId.get()));
bool loggingEnabled = false;
if (m_filterList && numActions)
m_filterList->GetLoggingEnabled(&loggingEnabled);
bool msgIsNew = true;
nsresult finalResult = NS_OK; // result of all actions
for (uint32_t actionIndex = 0; actionIndex < numActions && *applyMore;
actionIndex++) {
nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
if (!filterAction) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
("(Local) Filter action at index %" PRIu32 " invalid, skipping",
actionIndex));
continue;
}
nsMsgRuleActionType actionType;
if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Local) 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,
("(Local) Target URI for Copy/Move action is empty, skipping"));
// clang-format on
NS_ASSERTION(false, "actionTargetFolderUri is empty");
continue;
}
}
rv = NS_OK; // result of the current action
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);
if (NS_FAILED(rv)) break;
}
rv = msgHdr->OrFlags(nsMsgMessageFlags::Read,
&newFlags); // mark read in trash.
msgIsNew = false;
}
// FALLTHROUGH
[[fallthrough]];
case nsMsgFilterAction::MoveToFolder: {
// If moving to a different folder, do it.
if (!actionTargetFolderUri.IsEmpty() &&
!m_inboxUri.Equals(actionTargetFolderUri,
nsCaseInsensitiveCStringComparator)) {
nsCOMPtr<nsIMsgFolder> destIFolder;
// XXX TODO: why do we create the folder here, while we do not in
// the Copy action?
rv = GetOrCreateFolder(actionTargetFolderUri,
getter_AddRefs(destIFolder));
if (NS_FAILED(rv)) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
("(Local) Target Folder for Move action does not exist"));
break;
}
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.
// For pop3 and when mail moved to target folder by filter, if
// condition is false and else block is executed. So we don't have
// imap move coalescer, have to keep track of moved messages and
// target folders using m_filterTargetFoldersMsgMovedCount Map.
if (m_msgCopiedByFilter ||
StringBeginsWith(actionTargetFolderUri, "imap:"_ns)) {
if (!m_moveCoalescer)
m_moveCoalescer =
new nsImapMoveCoalescer(m_downloadFolder, m_msgWindow);
NS_ENSURE_TRUE(m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY);
rv = m_moveCoalescer->AddMove(destIFolder, msgKey);
msgIsNew = false;
if (NS_FAILED(rv)) break;
} else {
uint32_t old_flags;
msgHdr->GetFlags(&old_flags);
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
if (NS_SUCCEEDED(rv))
rv = msgStore->MoveNewlyDownloadedMessage(msgHdr, destIFolder,
&msgMoved);
if (NS_SUCCEEDED(rv) && !msgMoved)
rv = MoveIncorporatedMessage(msgHdr, m_mailDB, destIFolder,
filter, msgWindow);
m_msgMovedByFilter = NS_SUCCEEDED(rv);
if (m_msgMovedByFilter &&
!(old_flags & nsMsgMessageFlags::Read)) {
// Setting msgIsNew to false will execute the block at the end
// that decreases inbox's NumNewMessages.
msgIsNew = false;
if (!m_filterTargetFoldersMsgMovedCount) {
m_filterTargetFoldersMsgMovedCount = mozilla::MakeUnique<
nsTHashMap<nsCStringHashKey, int32_t>>();
}
int32_t targetFolderMsgMovedCount =
m_filterTargetFoldersMsgMovedCount->Get(
actionTargetFolderUri);
targetFolderMsgMovedCount++;
m_filterTargetFoldersMsgMovedCount->InsertOrUpdate(
actionTargetFolderUri, targetFolderMsgMovedCount);
}
if (!m_msgMovedByFilter /* == NS_FAILED(err) */) {
// XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
if (loggingEnabled) {
(void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
"filterFailureMoveFailed"_ns);
}
}
}
} else {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Local) Target folder is the same as source folder, "
"skipping"));
rv = NS_OK;
}
*applyMore = false;
} break;
case nsMsgFilterAction::CopyToFolder: {
nsCString uri;
rv = m_rootFolder->GetURI(uri);
if (!actionTargetFolderUri.IsEmpty() &&
!actionTargetFolderUri.Equals(uri)) {
nsCOMPtr<nsIMsgFolder> dstFolder;
nsCOMPtr<nsIMsgCopyService> copyService;
rv = GetExistingFolder(actionTargetFolderUri,
getter_AddRefs(dstFolder));
if (NS_FAILED(rv)) {
// Let's show a more specific warning.
MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
("(Local) Target Folder for Copy action does not exist"));
NS_WARNING("Target Folder does not exist.");
break;
}
copyService = do_GetService(
"@mozilla.org/messenger/messagecopyservice;1", &rv);
if (NS_SUCCEEDED(rv))
rv = copyService->CopyMessages(m_downloadFolder, {&*msgHdr},
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,
"filterFailureCopyFailed"_ns);
}
} else
m_msgCopiedByFilter = true;
} else {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Local) Target folder is the same as source folder, "
"skipping"));
break;
}
} break;
case nsMsgFilterAction::MarkRead:
msgIsNew = false;
MarkFilteredMessageRead(msgHdr);
rv = NS_OK;
break;
case nsMsgFilterAction::MarkUnread:
msgIsNew = true;
MarkFilteredMessageUnread(msgHdr);
rv = NS_OK;
break;
case nsMsgFilterAction::KillThread:
rv = msgHdr->SetUint32Property("ProtoThreadFlags",
nsMsgMessageFlags::Ignored);
break;
case nsMsgFilterAction::KillSubthread:
rv = msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
break;
case nsMsgFilterAction::WatchThread:
rv = msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
break;
case nsMsgFilterAction::MarkFlagged: {
rv = m_downloadFolder->MarkMessagesFlagged({&*msgHdr}, true);
} break;
case nsMsgFilterAction::ChangePriority: {
nsMsgPriorityValue filterPriority;
filterAction->GetPriority(&filterPriority);
rv = msgHdr->SetPriority(filterPriority);
} break;
case nsMsgFilterAction::AddTag: {
nsCString keyword;
filterAction->GetStrValue(keyword);
rv = m_downloadFolder->AddKeywordsToMessages({&*msgHdr}, keyword);
break;
}
case nsMsgFilterAction::JunkScore: {
nsAutoCString junkScoreStr;
int32_t junkScore;
filterAction->GetJunkScore(&junkScore);
junkScoreStr.AppendInt(junkScore);
if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) msgIsNew = false;
rv = msgHdr->SetStringProperty("junkscore", junkScoreStr);
msgHdr->SetStringProperty("junkscoreorigin", "filter"_ns);
} break;
case nsMsgFilterAction::Forward: {
nsCString forwardTo;
filterAction->GetStrValue(forwardTo);
m_forwardTo.AppendElement(forwardTo);
m_msgToForwardOrReply = msgHdr;
rv = NS_OK;
} break;
case nsMsgFilterAction::Reply: {
nsCString replyTemplateUri;
filterAction->GetStrValue(replyTemplateUri);
m_replyTemplateUri.AppendElement(replyTemplateUri);
m_msgToForwardOrReply = msgHdr;
m_ruleAction = filterAction;
m_filter = filter;
rv = NS_OK;
} break;
case nsMsgFilterAction::DeleteFromPop3Server: {
nsCOMPtr<nsIMsgFolder> downloadFolder;
msgHdr->GetFolder(getter_AddRefs(downloadFolder));
nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
do_QueryInterface(downloadFolder, &rv);
if (NS_FAILED(rv) || !localFolder) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
("(Local) Couldn't find local mail folder"));
break;
}
// This action ignores the deleteMailLeftOnServer preference
rv = localFolder->MarkMsgsOnPop3Server({&*msgHdr}, POP3_FORCE_DEL);
// If this is just a header, throw it away. It's useless now
// that the server copy is being deleted.
uint32_t flags = 0;
msgHdr->GetFlags(&flags);
if (flags & nsMsgMessageFlags::Partial) {
m_msgMovedByFilter = true;
msgIsNew = false;
}
} break;
case nsMsgFilterAction::FetchBodyFromPop3Server: {
nsCOMPtr<nsIMsgFolder> downloadFolder;
msgHdr->GetFolder(getter_AddRefs(downloadFolder));
nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
do_QueryInterface(downloadFolder, &rv);
if (NS_FAILED(rv) || !localFolder) {
MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
("(Local) Couldn't find local mail folder"));
break;
}
uint32_t flags = 0;
msgHdr->GetFlags(&flags);
if (flags & nsMsgMessageFlags::Partial) {
rv = localFolder->MarkMsgsOnPop3Server({&*msgHdr}, 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;
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);
} break;
default:
// XXX should not be reached. Check in debug build.
NS_ERROR("unexpected filter action");
rv = NS_ERROR_UNEXPECTED;
break;
}
}
if (NS_FAILED(rv)) {
finalResult = rv;
MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
("(Local) 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,
("(Local) Action execution succeeded"));
}
}
if (!msgIsNew) {
int32_t numNewMessages;
m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
if (numNewMessages > 0)
m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
m_numNotNewMessages++;
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Local) Message will not be marked new"));
}
MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
("(Local) Finished executing actions"));
return finalResult;
}