NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit()

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;
}