2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2011 Hiroyuki Yamamoto & The Claws Mail Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include <glib/gi18n.h>
31 #include "procheader.h"
33 #include "filtering.h"
34 #include "prefs_gtk.h"
36 #include "prefs_common.h"
38 #include "addr_compl.h"
42 GSList * pre_global_processing = NULL;
43 GSList * post_global_processing = NULL;
44 GSList * filtering_rules = NULL;
46 gboolean debug_filtering_session = FALSE;
48 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
50 #define STRLEN_WITH_CHECK(expr) \
51 strlen_with_check(#expr, __LINE__, expr)
53 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
58 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr?expr:"(null)");
63 FilteringAction * filteringaction_new(int type, int account_id,
65 gint labelcolor, gint score, gchar * header)
67 FilteringAction * action;
69 action = g_new0(FilteringAction, 1);
72 action->account_id = account_id;
74 action->destination = g_strdup(destination);
76 action->destination = NULL;
79 action->header = g_strdup(header);
81 action->header = NULL;
83 action->labelcolor = labelcolor;
84 action->score = score;
88 void filteringaction_free(FilteringAction * action)
90 cm_return_if_fail(action);
91 g_free(action->header);
92 g_free(action->destination);
96 static gint action_list_sort(gconstpointer a, gconstpointer b)
98 int first = filtering_is_final_action((FilteringAction *) a) ? 1 : 0;
99 int second = filtering_is_final_action((FilteringAction *) b) ? 1 : 0;
101 return (first - second);
104 GSList *filtering_action_list_sort(GSList *action_list)
106 return g_slist_sort(action_list, action_list_sort);
109 FilteringProp * filteringprop_new(gboolean enabled,
112 MatcherList * matchers,
113 GSList * action_list)
115 FilteringProp * filtering;
117 filtering = g_new0(FilteringProp, 1);
118 filtering->enabled = enabled;
119 filtering->name = name ? g_strdup(name): NULL;
120 filtering->account_id = account_id;
121 filtering->matchers = matchers;
122 filtering->action_list = filtering_action_list_sort(action_list);
127 static FilteringAction * filteringaction_copy(FilteringAction * src)
129 FilteringAction * new;
131 new = g_new0(FilteringAction, 1);
133 new->type = src->type;
134 new->account_id = src->account_id;
135 if (src->destination)
136 new->destination = g_strdup(src->destination);
138 new->destination = NULL;
139 new->labelcolor = src->labelcolor;
140 new->score = src->score;
145 FilteringProp * filteringprop_copy(FilteringProp *src)
150 new = g_new0(FilteringProp, 1);
151 new->matchers = g_new0(MatcherList, 1);
153 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
154 MatcherProp *matcher = (MatcherProp *)tmp->data;
156 new->matchers->matchers = g_slist_append(new->matchers->matchers,
157 matcherprop_copy(matcher));
161 new->matchers->bool_and = src->matchers->bool_and;
163 new->action_list = NULL;
165 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
166 FilteringAction *filtering_action;
168 filtering_action = tmp->data;
170 new->action_list = g_slist_append(new->action_list,
171 filteringaction_copy(filtering_action));
174 new->enabled = src->enabled;
175 new->name = g_strdup(src->name);
180 void filteringprop_free(FilteringProp * prop)
184 cm_return_if_fail(prop);
185 matcherlist_free(prop->matchers);
187 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
188 filteringaction_free(tmp->data);
190 g_slist_free(prop->action_list);
195 /* move and copy messages by batches to be faster on IMAP */
196 void filtering_move_and_copy_msgs(GSList *msgs)
198 GSList *messages = g_slist_copy(msgs);
199 FolderItem *last_item = NULL;
200 FiltOp cur_op = IS_NOTHING;
202 debug_print("checking %d messages\n", g_slist_length(msgs));
204 GSList *batch = NULL, *cur;
206 for (cur = messages; cur; cur = cur->next) {
207 MsgInfo *info = (MsgInfo *)cur->data;
208 if (last_item == NULL) {
209 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
210 last_item = info->to_filter_folder;
211 else if (info->filter_op == IS_DELE)
212 last_item = info->folder;
214 if (last_item == NULL)
216 if (cur_op == IS_NOTHING) {
217 if (info->filter_op == IS_COPY)
219 else if (info->filter_op == IS_MOVE)
221 else if (info->filter_op == IS_DELE)
224 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
225 if (info->to_filter_folder == last_item
226 && cur_op == info->filter_op) {
228 batch = g_slist_prepend(batch, info);
230 } else if (info->filter_op == IS_DELE) {
231 if (info->folder == last_item
232 && cur_op == info->filter_op) {
234 batch = g_slist_prepend(batch, info);
239 debug_print("no more messages to move/copy/del\n");
242 debug_print("%d messages to %s in %s\n", found,
243 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
244 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
246 for (cur = batch; cur; cur = cur->next) {
247 MsgInfo *info = (MsgInfo *)cur->data;
248 messages = g_slist_remove(messages, info);
249 info->to_filter_folder = NULL;
250 info->filter_op = IS_NOTHING;
252 batch = g_slist_reverse(batch);
253 if (g_slist_length(batch)) {
254 MsgInfo *info = (MsgInfo *)batch->data;
255 if (cur_op == IS_COPY && last_item != info->folder) {
256 folder_item_copy_msgs(last_item, batch);
257 } else if (cur_op == IS_MOVE && last_item != info->folder) {
258 if (folder_item_move_msgs(last_item, batch) < 0)
259 folder_item_move_msgs(
260 folder_get_default_inbox(),
262 } else if (cur_op == IS_DELE && last_item == info->folder) {
263 folder_item_remove_msgs(last_item, batch);
265 /* we don't reference the msginfos, because caller will do */
266 if (prefs_common.real_time_sync)
267 folder_item_synchronise(last_item);
275 /* we don't reference the msginfos, because caller will do */
276 g_slist_free(messages);
280 fitleringaction_apply
281 runs the action on one MsgInfo
282 return value : return TRUE if the action could be applied
285 #define FLUSH_COPY_IF_NEEDED(info) { \
286 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
287 debug_print("must debatch pending copy\n"); \
288 folder_item_copy_msg(info->to_filter_folder, info); \
289 info->filter_op = IS_NOTHING; \
293 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
295 FolderItem * dest_folder;
298 PrefsAccount * account;
301 switch(action->type) {
302 case MATCHACTION_MOVE:
304 folder_find_item_from_identifier(action->destination);
306 debug_print("*** folder not found '%s'\n",
307 action->destination ?action->destination :"(null)");
311 FLUSH_COPY_IF_NEEDED(info);
312 /* mark message to be moved */
313 info->filter_op = IS_MOVE;
314 info->to_filter_folder = dest_folder;
317 case MATCHACTION_COPY:
319 folder_find_item_from_identifier(action->destination);
322 debug_print("*** folder not found '%s'\n",
323 action->destination ?action->destination :"(null)");
327 FLUSH_COPY_IF_NEEDED(info);
328 /* mark message to be copied */
329 info->filter_op = IS_COPY;
330 info->to_filter_folder = dest_folder;
333 case MATCHACTION_SET_TAG:
334 case MATCHACTION_UNSET_TAG:
335 val = tags_get_id_for_str(action->destination);
337 debug_print("*** tag '%s' not found\n",
338 action->destination ?action->destination :"(null)");
341 FLUSH_COPY_IF_NEEDED(info);
342 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
345 case MATCHACTION_CLEAR_TAGS:
346 FLUSH_COPY_IF_NEEDED(info);
347 procmsg_msginfo_clear_tags(info);
350 case MATCHACTION_DELETE:
351 FLUSH_COPY_IF_NEEDED(info);
352 info->filter_op = IS_DELE;
355 case MATCHACTION_MARK:
356 FLUSH_COPY_IF_NEEDED(info);
357 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
360 case MATCHACTION_UNMARK:
361 FLUSH_COPY_IF_NEEDED(info);
362 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
365 case MATCHACTION_LOCK:
366 FLUSH_COPY_IF_NEEDED(info);
367 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
370 case MATCHACTION_UNLOCK:
371 FLUSH_COPY_IF_NEEDED(info);
372 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
375 case MATCHACTION_MARK_AS_READ:
376 FLUSH_COPY_IF_NEEDED(info);
377 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
380 case MATCHACTION_MARK_AS_UNREAD:
381 FLUSH_COPY_IF_NEEDED(info);
382 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
385 case MATCHACTION_MARK_AS_SPAM:
386 FLUSH_COPY_IF_NEEDED(info);
387 procmsg_spam_learner_learn(info, NULL, TRUE);
388 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
389 if (procmsg_spam_get_folder(info)) {
390 info->filter_op = IS_MOVE;
391 info->to_filter_folder = procmsg_spam_get_folder(info);
395 case MATCHACTION_MARK_AS_HAM:
396 FLUSH_COPY_IF_NEEDED(info);
397 procmsg_spam_learner_learn(info, NULL, FALSE);
398 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
401 case MATCHACTION_COLOR:
402 FLUSH_COPY_IF_NEEDED(info);
403 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
404 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
407 case MATCHACTION_FORWARD:
408 case MATCHACTION_FORWARD_AS_ATTACHMENT:
409 account = account_find_from_id(action->account_id);
410 compose = compose_forward(account, info,
411 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
413 compose_entry_append(compose, action->destination,
414 compose->account->protocol == A_NNTP
416 : COMPOSE_TO, PREF_NONE);
418 val = compose_send(compose);
420 return val == 0 ? TRUE : FALSE;
422 case MATCHACTION_REDIRECT:
423 account = account_find_from_id(action->account_id);
424 compose = compose_redirect(account, info, TRUE);
425 if (compose->account->protocol == A_NNTP)
428 compose_entry_append(compose, action->destination,
429 COMPOSE_TO, PREF_NONE);
431 val = compose_send(compose);
433 return val == 0 ? TRUE : FALSE;
435 case MATCHACTION_EXECUTE:
436 cmd = matching_build_command(action->destination, info);
440 if (system(cmd) == -1)
441 g_warning("couldn't run %s", cmd);
446 case MATCHACTION_SET_SCORE:
447 FLUSH_COPY_IF_NEEDED(info);
448 info->score = action->score;
451 case MATCHACTION_CHANGE_SCORE:
452 FLUSH_COPY_IF_NEEDED(info);
453 info->score += action->score;
456 case MATCHACTION_STOP:
459 case MATCHACTION_HIDE:
460 FLUSH_COPY_IF_NEEDED(info);
464 case MATCHACTION_IGNORE:
465 FLUSH_COPY_IF_NEEDED(info);
466 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
469 case MATCHACTION_WATCH:
470 FLUSH_COPY_IF_NEEDED(info);
471 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
474 case MATCHACTION_ADD_TO_ADDRESSBOOK:
476 AddressDataSource *book = NULL;
477 AddressBookFile *abf = NULL;
478 ItemFolder *folder = NULL;
483 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
484 g_warning("addressbook folder not found '%s'\n", action->destination?action->destination:"(null)");
488 g_warning("addressbook_peek_folder_exists returned NULL book\n");
492 abf = book->rawDataSource;
495 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
496 header = procheader_parse_header(buf);
498 /* add all addresses that are not already in */
499 if (header && *header->body && (*header->body != '\0')) {
500 GSList *address_list = NULL;
504 if (action->destination == NULL ||
505 strcasecmp(action->destination, "Any") == 0 ||
506 *(action->destination) == '\0')
509 path = action->destination;
510 start_address_completion(path);
512 address_list = address_list_append(address_list, header->body);
513 for (walk = address_list; walk != NULL; walk = walk->next) {
514 gchar *stripped_addr = g_strdup(walk->data);
515 extract_address(stripped_addr);
517 if (complete_matches_found(walk->data) == 0) {
518 debug_print("adding address '%s' to addressbook '%s'\n",
519 stripped_addr, action->destination);
520 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
521 g_warning("contact could not been added\n");
525 debug_print("address '%s' already found in addressbook '%s', skipping\n",
526 stripped_addr, action->destination);
528 g_free(stripped_addr);
531 g_slist_free(address_list);
532 end_address_completion();
534 g_warning("header '%s' not set or empty\n", action->header?action->header:"(null)");
536 return (errors == 0);
545 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
548 cm_return_val_if_fail(action_list, FALSE);
549 cm_return_val_if_fail(info, FALSE);
550 for (p = action_list; p && p->data; p = g_slist_next(p)) {
551 FilteringAction *a = (FilteringAction *) p->data;
552 if (filteringaction_apply(a, info)) {
553 if (filtering_is_final_action(a))
562 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
563 PrefsAccount *ac_prefs)
565 /* this function returns true if a filtering rule applies regarding to its account
566 data and if it does, if the conditions list match.
568 per-account data of a filtering rule is either matched against current account
569 when filtering is done manually, or against the account currently used for
570 retrieving messages when it's an manual or automatic fetching of messages.
571 per-account data match doesn't apply to pre-/post-/folder-processing rules.
573 when filtering messages manually:
574 - either the filtering rule is not account-based and it will be processed
575 - or it's per-account and we check if we HAVE TO match it against the current
576 account (according to user-land settings, per-account rules might have to
577 be skipped, or only the rules that match the current account have to be
578 applied, or all rules will have to be applied regardless to if they are
579 account-based or not)
581 notes about debugging output in that function:
582 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
583 no debug output is done when filtering_debug_level is low
586 gboolean matches = FALSE;
588 if (ac_prefs != NULL) {
589 matches = ((filtering->account_id == 0)
590 || (filtering->account_id == ac_prefs->account_id));
593 if (debug_filtering_session) {
594 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
595 if (filtering->account_id == 0) {
596 log_status_ok(LOG_DEBUG_FILTERING,
597 _("rule is not account-based\n"));
599 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
600 log_status_ok(LOG_DEBUG_FILTERING,
601 _("rule is account-based [id=%d, name='%s'], "
602 "matching the account currently used to retrieve messages\n"),
603 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
609 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
610 log_status_skip(LOG_DEBUG_FILTERING,
611 _("rule is account-based, "
612 "not matching the account currently used to retrieve messages\n"));
614 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
615 PrefsAccount *account = account_find_from_id(filtering->account_id);
617 log_status_skip(LOG_DEBUG_FILTERING,
618 _("rule is account-based [id=%d, name='%s'], "
619 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
620 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
621 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
627 switch (prefs_common.apply_per_account_filtering_rules) {
628 case FILTERING_ACCOUNT_RULES_FORCE:
629 /* apply filtering rules regardless to the account info */
633 if (debug_filtering_session) {
634 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
635 if (filtering->account_id == 0) {
636 log_status_ok(LOG_DEBUG_FILTERING,
637 _("rule is not account-based, "
638 "all rules are applied on user request anyway\n"));
640 PrefsAccount *account = account_find_from_id(filtering->account_id);
642 log_status_ok(LOG_DEBUG_FILTERING,
643 _("rule is account-based [id=%d, name='%s'], "
644 "but all rules are applied on user request\n"),
645 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
650 case FILTERING_ACCOUNT_RULES_SKIP:
651 /* don't apply filtering rules that belong to an account */
652 matches = (filtering->account_id == 0);
655 if (debug_filtering_session) {
657 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
658 PrefsAccount *account = account_find_from_id(filtering->account_id);
660 log_status_skip(LOG_DEBUG_FILTERING,
661 _("rule is account-based [id=%d, name='%s'], "
662 "skipped on user request\n"),
663 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
665 log_status_skip(LOG_DEBUG_FILTERING,
666 _("rule is account-based, "
667 "skipped on user request\n"));
670 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
671 log_status_ok(LOG_DEBUG_FILTERING,
672 _("rule is not account-based\n"));
677 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
678 matches = ((filtering->account_id == 0)
679 || (filtering->account_id == cur_account->account_id));
682 if (debug_filtering_session) {
684 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
685 PrefsAccount *account = account_find_from_id(filtering->account_id);
687 log_status_skip(LOG_DEBUG_FILTERING,
688 _("rule is account-based [id=%d, name='%s'], "
689 "not matching current account [id=%d, name='%s']\n"),
690 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
691 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
693 log_status_skip(LOG_DEBUG_FILTERING,
694 _("rule is account-based, "
695 "not matching current account\n"));
698 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
699 if (filtering->account_id == 0) {
700 log_status_ok(LOG_DEBUG_FILTERING,
701 _("rule is not account-based\n"));
703 PrefsAccount *account = account_find_from_id(filtering->account_id);
705 log_status_ok(LOG_DEBUG_FILTERING,
706 _("rule is account-based [id=%d, name='%s'], "
707 "current account [id=%d, name='%s']\n"),
708 account->account_id, account?account->account_name:_("NON_EXISTENT"),
709 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
718 return matches && matcherlist_match(filtering->matchers, info);
722 *\brief Apply a rule on message.
724 *\param filtering List of filtering rules.
725 *\param info Message to apply rules on.
726 *\param final Variable returning TRUE or FALSE if one of the
727 * encountered actions was final.
728 * See also \ref filtering_is_final_action.
730 *\return gboolean TRUE to continue applying rules.
732 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
735 gboolean result = TRUE;
740 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
741 FilteringAction * action;
744 buf = filteringaction_to_string(action);
745 if (debug_filtering_session)
746 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
748 if (FALSE == (result = filteringaction_apply(action, info))) {
749 if (debug_filtering_session) {
750 if (action->type != MATCHACTION_STOP)
751 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
752 log_print(LOG_DEBUG_FILTERING,
753 _("no further processing after action [ %s ]\n"), buf);
755 debug_print("No further processing after rule %s\n", buf);
758 if (filtering_is_final_action(action)) {
768 *\brief Check if an action is "final", i.e. should break further
771 *\param filtering_action Action to check.
773 *\return gboolean TRUE if \a filtering_action is final.
775 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
777 switch(filtering_action->type) {
778 case MATCHACTION_MOVE:
779 case MATCHACTION_DELETE:
780 case MATCHACTION_STOP:
781 case MATCHACTION_MARK_AS_SPAM:
782 return TRUE; /* MsgInfo invalid for message */
788 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
794 cm_return_val_if_fail(info != NULL, TRUE);
796 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
797 FilteringProp * filtering = (FilteringProp *) l->data;
799 if (filtering->enabled) {
800 if (debug_filtering_session) {
801 gchar *buf = filteringprop_to_string(filtering);
802 if (filtering->name && *filtering->name != '\0') {
803 log_print(LOG_DEBUG_FILTERING,
804 _("processing rule '%s' [ %s ]\n"),
805 filtering->name, buf);
807 log_print(LOG_DEBUG_FILTERING,
808 _("processing rule <unnamed> [ %s ]\n"),
814 if (filtering_match_condition(filtering, info, ac_prefs)) {
815 apply_next = filtering_apply_rule(filtering, info, &final);
821 if (debug_filtering_session) {
822 gchar *buf = filteringprop_to_string(filtering);
823 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
824 if (filtering->name && *filtering->name != '\0') {
825 log_status_skip(LOG_DEBUG_FILTERING,
826 _("disabled rule '%s' [ %s ]\n"),
827 filtering->name, buf);
829 log_status_skip(LOG_DEBUG_FILTERING,
830 _("disabled rule <unnamed> [ %s ]\n"),
839 /* put in inbox if the last rule was not a final one, or
840 * a final rule could not be applied.
841 * Either of these cases is likely. */
842 if (!final || !apply_next) {
850 *\brief Filter a message against a list of rules.
852 *\param flist List of filter rules.
853 *\param info Message.
855 *\return gboolean TRUE if filter rules handled the message.
857 *\note Returning FALSE means the message was not handled,
858 * and that the calling code should do the default
859 * processing. E.g. \ref inc.c::inc_start moves the
860 * message to the inbox.
862 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
863 FilteringInvocationType context, gchar *extra_info)
867 if (prefs_common.enable_filtering_debug) {
868 gchar *tmp = _("undetermined");
871 case FILTERING_INCORPORATION:
872 tmp = _("incorporation");
873 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
875 case FILTERING_MANUALLY:
877 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
879 case FILTERING_FOLDER_PROCESSING:
880 tmp = _("folder processing");
881 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
883 case FILTERING_PRE_PROCESSING:
884 tmp = _("pre-processing");
885 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
887 case FILTERING_POST_PROCESSING:
888 tmp = _("post-processing");
889 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
892 debug_filtering_session = FALSE;
896 debug_filtering_session = FALSE;
898 if (debug_filtering_session) {
899 gchar *file = procmsg_get_message_file_path(info);
900 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
902 /* show context info and essential info about the message */
903 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
904 log_print(LOG_DEBUG_FILTERING,
905 _("filtering message (%s%s%s)\n"
906 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
907 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
908 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
909 spc, prefs_common_translated_header_name("From:"), info->from,
910 spc, prefs_common_translated_header_name("To:"), info->to,
911 spc, prefs_common_translated_header_name("Subject:"), info->subject);
913 log_print(LOG_DEBUG_FILTERING,
914 _("filtering message (%s%s%s)\n"
915 "%smessage file: %s\n"),
916 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
923 debug_filtering_session = FALSE;
925 ret = filter_msginfo(flist, info, ac_prefs);
926 debug_filtering_session = FALSE;
930 gchar *filteringaction_to_string(FilteringAction *action)
932 const gchar *command_str;
934 gchar * quoted_header;
935 GString *dest = g_string_new("");
936 gchar *deststr = NULL;
938 command_str = get_matchparser_tab_str(action->type);
940 if (command_str == NULL)
943 switch(action->type) {
944 case MATCHACTION_MOVE:
945 case MATCHACTION_COPY:
946 case MATCHACTION_EXECUTE:
947 case MATCHACTION_SET_TAG:
948 case MATCHACTION_UNSET_TAG:
949 quoted_dest = matcher_quote_str(action->destination);
950 g_string_append_printf(dest, "%s \"%s\"", command_str, quoted_dest);
954 case MATCHACTION_DELETE:
955 case MATCHACTION_MARK:
956 case MATCHACTION_UNMARK:
957 case MATCHACTION_LOCK:
958 case MATCHACTION_UNLOCK:
959 case MATCHACTION_MARK_AS_READ:
960 case MATCHACTION_MARK_AS_UNREAD:
961 case MATCHACTION_MARK_AS_SPAM:
962 case MATCHACTION_MARK_AS_HAM:
963 case MATCHACTION_STOP:
964 case MATCHACTION_HIDE:
965 case MATCHACTION_IGNORE:
966 case MATCHACTION_WATCH:
967 case MATCHACTION_CLEAR_TAGS:
968 g_string_append_printf(dest, "%s", command_str);
971 case MATCHACTION_REDIRECT:
972 case MATCHACTION_FORWARD:
973 case MATCHACTION_FORWARD_AS_ATTACHMENT:
974 quoted_dest = matcher_quote_str(action->destination);
975 g_string_append_printf(dest, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
979 case MATCHACTION_COLOR:
980 g_string_append_printf(dest, "%s %d", command_str, action->labelcolor);
983 case MATCHACTION_CHANGE_SCORE:
984 case MATCHACTION_SET_SCORE:
985 g_string_append_printf(dest, "%s %d", command_str, action->score);
988 case MATCHACTION_ADD_TO_ADDRESSBOOK:
989 quoted_header = matcher_quote_str(action->header);
990 quoted_dest = matcher_quote_str(action->destination);
991 g_string_append_printf(dest, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
993 g_free(quoted_header);
1000 g_string_free(dest, FALSE);
1004 gchar * filteringaction_list_to_string(GSList * action_list)
1006 gchar *action_list_str;
1010 action_list_str = NULL;
1011 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
1013 FilteringAction * action;
1017 action_str = filteringaction_to_string(action);
1019 if (action_list_str != NULL) {
1020 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1021 g_free(action_list_str);
1024 list_str = g_strdup(action_str);
1027 action_list_str = list_str;
1030 return action_list_str;
1033 gchar * filteringprop_to_string(FilteringProp * prop)
1036 gchar *action_list_str;
1037 gchar *filtering_str;
1042 action_list_str = filteringaction_list_to_string(prop->action_list);
1044 if (action_list_str == NULL)
1047 list_str = matcherlist_to_string(prop->matchers);
1049 if (list_str == NULL) {
1050 g_free(action_list_str);
1054 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1055 g_free(action_list_str);
1058 return filtering_str;
1061 static void prefs_filtering_free(GSList * prefs_filtering)
1063 while (prefs_filtering != NULL) {
1064 FilteringProp * filtering = (FilteringProp *)
1065 prefs_filtering->data;
1066 filteringprop_free(filtering);
1067 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1071 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1073 FolderItem *item = node->data;
1075 cm_return_val_if_fail(item, FALSE);
1076 cm_return_val_if_fail(item->prefs, FALSE);
1078 prefs_filtering_free(item->prefs->processing);
1079 item->prefs->processing = NULL;
1084 void prefs_filtering_clear(void)
1088 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1091 folder = (Folder *) cur->data;
1092 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1093 prefs_filtering_free_func, NULL);
1096 prefs_filtering_free(filtering_rules);
1097 filtering_rules = NULL;
1098 prefs_filtering_free(pre_global_processing);
1099 pre_global_processing = NULL;
1100 prefs_filtering_free(post_global_processing);
1101 post_global_processing = NULL;
1104 void prefs_filtering_clear_folder(Folder *folder)
1106 cm_return_if_fail(folder);
1107 cm_return_if_fail(folder->node);
1109 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1110 prefs_filtering_free_func, NULL);
1111 /* FIXME: Note folder settings were changed, where the updates? */
1114 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1115 /* return TRUE if there's at least one per-account filtering rule */
1119 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1120 FilteringProp * filtering = (FilteringProp *) l->data;
1122 if (filtering->enabled && (filtering->account_id != 0)) {