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 FilteringProp * filteringprop_new(gboolean enabled,
99 MatcherList * matchers,
100 GSList * action_list)
102 FilteringProp * filtering;
104 filtering = g_new0(FilteringProp, 1);
105 filtering->enabled = enabled;
106 filtering->name = name ? g_strdup(name): NULL;
107 filtering->account_id = account_id;
108 filtering->matchers = matchers;
109 filtering->action_list = action_list;
114 static FilteringAction * filteringaction_copy(FilteringAction * src)
116 FilteringAction * new;
118 new = g_new0(FilteringAction, 1);
120 new->type = src->type;
121 new->account_id = src->account_id;
122 if (src->destination)
123 new->destination = g_strdup(src->destination);
125 new->destination = NULL;
126 new->labelcolor = src->labelcolor;
127 new->score = src->score;
132 FilteringProp * filteringprop_copy(FilteringProp *src)
137 new = g_new0(FilteringProp, 1);
138 new->matchers = g_new0(MatcherList, 1);
140 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
141 MatcherProp *matcher = (MatcherProp *)tmp->data;
143 new->matchers->matchers = g_slist_append(new->matchers->matchers,
144 matcherprop_copy(matcher));
148 new->matchers->bool_and = src->matchers->bool_and;
150 new->action_list = NULL;
152 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
153 FilteringAction *filtering_action;
155 filtering_action = tmp->data;
157 new->action_list = g_slist_append(new->action_list,
158 filteringaction_copy(filtering_action));
161 new->enabled = src->enabled;
162 new->name = g_strdup(src->name);
167 void filteringprop_free(FilteringProp * prop)
171 cm_return_if_fail(prop);
172 matcherlist_free(prop->matchers);
174 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
175 filteringaction_free(tmp->data);
177 g_slist_free(prop->action_list);
182 /* move and copy messages by batches to be faster on IMAP */
183 void filtering_move_and_copy_msgs(GSList *msgs)
185 GSList *messages = g_slist_copy(msgs);
186 FolderItem *last_item = NULL;
187 FiltOp cur_op = IS_NOTHING;
189 debug_print("checking %d messages\n", g_slist_length(msgs));
191 GSList *batch = NULL, *cur;
193 for (cur = messages; cur; cur = cur->next) {
194 MsgInfo *info = (MsgInfo *)cur->data;
195 if (last_item == NULL) {
196 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
197 last_item = info->to_filter_folder;
198 else if (info->filter_op == IS_DELE)
199 last_item = info->folder;
201 if (last_item == NULL)
203 if (cur_op == IS_NOTHING) {
204 if (info->filter_op == IS_COPY)
206 else if (info->filter_op == IS_MOVE)
208 else if (info->filter_op == IS_DELE)
211 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
212 if (info->to_filter_folder == last_item
213 && cur_op == info->filter_op) {
215 batch = g_slist_prepend(batch, info);
217 } else if (info->filter_op == IS_DELE) {
218 if (info->folder == last_item
219 && cur_op == info->filter_op) {
221 batch = g_slist_prepend(batch, info);
226 debug_print("no more messages to move/copy/del\n");
229 debug_print("%d messages to %s in %s\n", found,
230 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
231 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
233 for (cur = batch; cur; cur = cur->next) {
234 MsgInfo *info = (MsgInfo *)cur->data;
235 messages = g_slist_remove(messages, info);
236 info->to_filter_folder = NULL;
237 info->filter_op = IS_NOTHING;
239 batch = g_slist_reverse(batch);
240 if (g_slist_length(batch)) {
241 MsgInfo *info = (MsgInfo *)batch->data;
242 if (cur_op == IS_COPY && last_item != info->folder) {
243 folder_item_copy_msgs(last_item, batch);
244 } else if (cur_op == IS_MOVE && last_item != info->folder) {
245 if (folder_item_move_msgs(last_item, batch) < 0)
246 folder_item_move_msgs(
247 folder_get_default_inbox(),
249 } else if (cur_op == IS_DELE && last_item == info->folder) {
250 folder_item_remove_msgs(last_item, batch);
252 /* we don't reference the msginfos, because caller will do */
253 if (prefs_common.real_time_sync)
254 folder_item_synchronise(last_item);
262 /* we don't reference the msginfos, because caller will do */
263 g_slist_free(messages);
267 fitleringaction_apply
268 runs the action on one MsgInfo
269 return value : return TRUE if the action could be applied
272 #define FLUSH_COPY_IF_NEEDED(info) { \
273 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
274 debug_print("must debatch pending copy\n"); \
275 folder_item_copy_msg(info->to_filter_folder, info); \
276 info->filter_op = IS_NOTHING; \
280 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
282 FolderItem * dest_folder;
285 PrefsAccount * account;
288 switch(action->type) {
289 case MATCHACTION_MOVE:
291 folder_find_item_from_identifier(action->destination);
293 debug_print("*** folder not found '%s'\n",
294 action->destination ?action->destination :"(null)");
298 FLUSH_COPY_IF_NEEDED(info);
299 /* mark message to be moved */
300 info->filter_op = IS_MOVE;
301 info->to_filter_folder = dest_folder;
304 case MATCHACTION_COPY:
306 folder_find_item_from_identifier(action->destination);
309 debug_print("*** folder not found '%s'\n",
310 action->destination ?action->destination :"(null)");
314 FLUSH_COPY_IF_NEEDED(info);
315 /* mark message to be copied */
316 info->filter_op = IS_COPY;
317 info->to_filter_folder = dest_folder;
320 case MATCHACTION_SET_TAG:
321 case MATCHACTION_UNSET_TAG:
322 val = tags_get_id_for_str(action->destination);
324 debug_print("*** tag '%s' not found\n",
325 action->destination ?action->destination :"(null)");
328 FLUSH_COPY_IF_NEEDED(info);
329 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
332 case MATCHACTION_CLEAR_TAGS:
333 FLUSH_COPY_IF_NEEDED(info);
334 procmsg_msginfo_clear_tags(info);
337 case MATCHACTION_DELETE:
338 FLUSH_COPY_IF_NEEDED(info);
339 info->filter_op = IS_DELE;
342 case MATCHACTION_MARK:
343 FLUSH_COPY_IF_NEEDED(info);
344 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
347 case MATCHACTION_UNMARK:
348 FLUSH_COPY_IF_NEEDED(info);
349 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
352 case MATCHACTION_LOCK:
353 FLUSH_COPY_IF_NEEDED(info);
354 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
357 case MATCHACTION_UNLOCK:
358 FLUSH_COPY_IF_NEEDED(info);
359 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
362 case MATCHACTION_MARK_AS_READ:
363 FLUSH_COPY_IF_NEEDED(info);
364 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
367 case MATCHACTION_MARK_AS_UNREAD:
368 FLUSH_COPY_IF_NEEDED(info);
369 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
372 case MATCHACTION_MARK_AS_SPAM:
373 FLUSH_COPY_IF_NEEDED(info);
374 procmsg_spam_learner_learn(info, NULL, TRUE);
375 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
376 if (procmsg_spam_get_folder(info)) {
377 info->filter_op = IS_MOVE;
378 info->to_filter_folder = procmsg_spam_get_folder(info);
382 case MATCHACTION_MARK_AS_HAM:
383 FLUSH_COPY_IF_NEEDED(info);
384 procmsg_spam_learner_learn(info, NULL, FALSE);
385 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
388 case MATCHACTION_COLOR:
389 FLUSH_COPY_IF_NEEDED(info);
390 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
391 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
394 case MATCHACTION_FORWARD:
395 case MATCHACTION_FORWARD_AS_ATTACHMENT:
396 account = account_find_from_id(action->account_id);
397 compose = compose_forward(account, info,
398 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
400 compose_entry_append(compose, action->destination,
401 compose->account->protocol == A_NNTP
403 : COMPOSE_TO, PREF_NONE);
405 val = compose_send(compose);
407 return val == 0 ? TRUE : FALSE;
409 case MATCHACTION_REDIRECT:
410 account = account_find_from_id(action->account_id);
411 compose = compose_redirect(account, info, TRUE);
412 if (compose->account->protocol == A_NNTP)
415 compose_entry_append(compose, action->destination,
416 COMPOSE_TO, PREF_NONE);
418 val = compose_send(compose);
420 return val == 0 ? TRUE : FALSE;
422 case MATCHACTION_EXECUTE:
423 cmd = matching_build_command(action->destination, info);
427 if (system(cmd) == -1)
428 g_warning("couldn't run %s", cmd);
433 case MATCHACTION_SET_SCORE:
434 FLUSH_COPY_IF_NEEDED(info);
435 info->score = action->score;
438 case MATCHACTION_CHANGE_SCORE:
439 FLUSH_COPY_IF_NEEDED(info);
440 info->score += action->score;
443 case MATCHACTION_STOP:
446 case MATCHACTION_HIDE:
447 FLUSH_COPY_IF_NEEDED(info);
451 case MATCHACTION_IGNORE:
452 FLUSH_COPY_IF_NEEDED(info);
453 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
456 case MATCHACTION_WATCH:
457 FLUSH_COPY_IF_NEEDED(info);
458 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
461 case MATCHACTION_ADD_TO_ADDRESSBOOK:
463 AddressDataSource *book = NULL;
464 AddressBookFile *abf = NULL;
465 ItemFolder *folder = NULL;
470 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
471 g_warning("addressbook folder not found '%s'\n", action->destination?action->destination:"(null)");
475 g_warning("addressbook_peek_folder_exists returned NULL book\n");
479 abf = book->rawDataSource;
482 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
483 header = procheader_parse_header(buf);
485 /* add all addresses that are not already in */
486 if (header && *header->body && (*header->body != '\0')) {
487 GSList *address_list = NULL;
491 if (action->destination == NULL ||
492 strcasecmp(action->destination, "Any") == 0 ||
493 *(action->destination) == '\0')
496 path = action->destination;
497 start_address_completion(path);
499 address_list = address_list_append(address_list, header->body);
500 for (walk = address_list; walk != NULL; walk = walk->next) {
501 gchar *stripped_addr = g_strdup(walk->data);
502 extract_address(stripped_addr);
504 if (complete_matches_found(walk->data) == 0) {
505 debug_print("adding address '%s' to addressbook '%s'\n",
506 stripped_addr, action->destination);
507 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
508 g_warning("contact could not been added\n");
512 debug_print("address '%s' already found in addressbook '%s', skipping\n",
513 stripped_addr, action->destination);
515 g_free(stripped_addr);
518 g_slist_free(address_list);
519 end_address_completion();
521 g_warning("header '%s' not set or empty\n", action->header?action->header:"(null)");
523 return (errors == 0);
532 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
535 cm_return_val_if_fail(action_list, FALSE);
536 cm_return_val_if_fail(info, FALSE);
537 for (p = action_list; p && p->data; p = g_slist_next(p)) {
538 FilteringAction *a = (FilteringAction *) p->data;
539 if (filteringaction_apply(a, info)) {
540 if (filtering_is_final_action(a))
549 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
550 PrefsAccount *ac_prefs)
552 /* this function returns true if a filtering rule applies regarding to its account
553 data and if it does, if the conditions list match.
555 per-account data of a filtering rule is either matched against current account
556 when filtering is done manually, or against the account currently used for
557 retrieving messages when it's an manual or automatic fetching of messages.
558 per-account data match doesn't apply to pre-/post-/folder-processing rules.
560 when filtering messages manually:
561 - either the filtering rule is not account-based and it will be processed
562 - or it's per-account and we check if we HAVE TO match it against the current
563 account (according to user-land settings, per-account rules might have to
564 be skipped, or only the rules that match the current account have to be
565 applied, or all rules will have to be applied regardless to if they are
566 account-based or not)
568 notes about debugging output in that function:
569 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
570 no debug output is done when filtering_debug_level is low
573 gboolean matches = FALSE;
575 if (ac_prefs != NULL) {
576 matches = ((filtering->account_id == 0)
577 || (filtering->account_id == ac_prefs->account_id));
580 if (debug_filtering_session) {
581 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
582 if (filtering->account_id == 0) {
583 log_status_ok(LOG_DEBUG_FILTERING,
584 _("rule is not account-based\n"));
586 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
587 log_status_ok(LOG_DEBUG_FILTERING,
588 _("rule is account-based [id=%d, name='%s'], "
589 "matching the account currently used to retrieve messages\n"),
590 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
596 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
597 log_status_skip(LOG_DEBUG_FILTERING,
598 _("rule is account-based, "
599 "not matching the account currently used to retrieve messages\n"));
601 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
602 PrefsAccount *account = account_find_from_id(filtering->account_id);
604 log_status_skip(LOG_DEBUG_FILTERING,
605 _("rule is account-based [id=%d, name='%s'], "
606 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
607 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
608 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
614 switch (prefs_common.apply_per_account_filtering_rules) {
615 case FILTERING_ACCOUNT_RULES_FORCE:
616 /* apply filtering rules regardless to the account info */
620 if (debug_filtering_session) {
621 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
622 if (filtering->account_id == 0) {
623 log_status_ok(LOG_DEBUG_FILTERING,
624 _("rule is not account-based, "
625 "all rules are applied on user request anyway\n"));
627 PrefsAccount *account = account_find_from_id(filtering->account_id);
629 log_status_ok(LOG_DEBUG_FILTERING,
630 _("rule is account-based [id=%d, name='%s'], "
631 "but all rules are applied on user request\n"),
632 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
637 case FILTERING_ACCOUNT_RULES_SKIP:
638 /* don't apply filtering rules that belong to an account */
639 matches = (filtering->account_id == 0);
642 if (debug_filtering_session) {
644 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
645 PrefsAccount *account = account_find_from_id(filtering->account_id);
647 log_status_skip(LOG_DEBUG_FILTERING,
648 _("rule is account-based [id=%d, name='%s'], "
649 "skipped on user request\n"),
650 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
652 log_status_skip(LOG_DEBUG_FILTERING,
653 _("rule is account-based, "
654 "skipped on user request\n"));
657 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
658 log_status_ok(LOG_DEBUG_FILTERING,
659 _("rule is not account-based\n"));
664 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
665 matches = ((filtering->account_id == 0)
666 || (filtering->account_id == cur_account->account_id));
669 if (debug_filtering_session) {
671 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
672 PrefsAccount *account = account_find_from_id(filtering->account_id);
674 log_status_skip(LOG_DEBUG_FILTERING,
675 _("rule is account-based [id=%d, name='%s'], "
676 "not matching current account [id=%d, name='%s']\n"),
677 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
678 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
680 log_status_skip(LOG_DEBUG_FILTERING,
681 _("rule is account-based, "
682 "not matching current account\n"));
685 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
686 if (filtering->account_id == 0) {
687 log_status_ok(LOG_DEBUG_FILTERING,
688 _("rule is not account-based\n"));
690 PrefsAccount *account = account_find_from_id(filtering->account_id);
692 log_status_ok(LOG_DEBUG_FILTERING,
693 _("rule is account-based [id=%d, name='%s'], "
694 "current account [id=%d, name='%s']\n"),
695 account->account_id, account?account->account_name:_("NON_EXISTENT"),
696 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
705 return matches && matcherlist_match(filtering->matchers, info);
709 *\brief Apply a rule on message.
711 *\param filtering List of filtering rules.
712 *\param info Message to apply rules on.
713 *\param final Variable returning TRUE or FALSE if one of the
714 * encountered actions was final.
715 * See also \ref filtering_is_final_action.
717 *\return gboolean TRUE to continue applying rules.
719 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
722 gboolean result = TRUE;
727 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
728 FilteringAction * action;
731 buf = filteringaction_to_string(action);
732 if (debug_filtering_session)
733 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
735 if (FALSE == (result = filteringaction_apply(action, info))) {
736 if (debug_filtering_session) {
737 if (action->type != MATCHACTION_STOP)
738 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
739 log_print(LOG_DEBUG_FILTERING,
740 _("no further processing after action [ %s ]\n"), buf);
742 debug_print("No further processing after rule %s\n", buf);
745 if (filtering_is_final_action(action)) {
755 *\brief Check if an action is "final", i.e. should break further
758 *\param filtering_action Action to check.
760 *\return gboolean TRUE if \a filtering_action is final.
762 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
764 switch(filtering_action->type) {
765 case MATCHACTION_MOVE:
766 case MATCHACTION_DELETE:
767 case MATCHACTION_STOP:
768 case MATCHACTION_MARK_AS_SPAM:
769 return TRUE; /* MsgInfo invalid for message */
775 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
781 cm_return_val_if_fail(info != NULL, TRUE);
783 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
784 FilteringProp * filtering = (FilteringProp *) l->data;
786 if (filtering->enabled) {
787 if (debug_filtering_session) {
788 gchar *buf = filteringprop_to_string(filtering);
789 if (filtering->name && *filtering->name != '\0') {
790 log_print(LOG_DEBUG_FILTERING,
791 _("processing rule '%s' [ %s ]\n"),
792 filtering->name, buf);
794 log_print(LOG_DEBUG_FILTERING,
795 _("processing rule <unnamed> [ %s ]\n"),
801 if (filtering_match_condition(filtering, info, ac_prefs)) {
802 apply_next = filtering_apply_rule(filtering, info, &final);
808 if (debug_filtering_session) {
809 gchar *buf = filteringprop_to_string(filtering);
810 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
811 if (filtering->name && *filtering->name != '\0') {
812 log_status_skip(LOG_DEBUG_FILTERING,
813 _("disabled rule '%s' [ %s ]\n"),
814 filtering->name, buf);
816 log_status_skip(LOG_DEBUG_FILTERING,
817 _("disabled rule <unnamed> [ %s ]\n"),
826 /* put in inbox if the last rule was not a final one, or
827 * a final rule could not be applied.
828 * Either of these cases is likely. */
829 if (!final || !apply_next) {
837 *\brief Filter a message against a list of rules.
839 *\param flist List of filter rules.
840 *\param info Message.
842 *\return gboolean TRUE if filter rules handled the message.
844 *\note Returning FALSE means the message was not handled,
845 * and that the calling code should do the default
846 * processing. E.g. \ref inc.c::inc_start moves the
847 * message to the inbox.
849 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
850 FilteringInvocationType context, gchar *extra_info)
854 if (prefs_common.enable_filtering_debug) {
855 gchar *tmp = _("undetermined");
858 case FILTERING_INCORPORATION:
859 tmp = _("incorporation");
860 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
862 case FILTERING_MANUALLY:
864 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
866 case FILTERING_FOLDER_PROCESSING:
867 tmp = _("folder processing");
868 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
870 case FILTERING_PRE_PROCESSING:
871 tmp = _("pre-processing");
872 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
874 case FILTERING_POST_PROCESSING:
875 tmp = _("post-processing");
876 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
879 debug_filtering_session = FALSE;
883 debug_filtering_session = FALSE;
885 if (debug_filtering_session) {
886 gchar *file = procmsg_get_message_file_path(info);
887 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
889 /* show context info and essential info about the message */
890 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
891 log_print(LOG_DEBUG_FILTERING,
892 _("filtering message (%s%s%s)\n"
893 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
894 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
895 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
896 spc, prefs_common_translated_header_name("From:"), info->from,
897 spc, prefs_common_translated_header_name("To:"), info->to,
898 spc, prefs_common_translated_header_name("Subject:"), info->subject);
900 log_print(LOG_DEBUG_FILTERING,
901 _("filtering message (%s%s%s)\n"
902 "%smessage file: %s\n"),
903 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
910 debug_filtering_session = FALSE;
912 ret = filter_msginfo(flist, info, ac_prefs);
913 debug_filtering_session = FALSE;
917 gchar *filteringaction_to_string(FilteringAction *action)
919 const gchar *command_str;
921 gchar * quoted_header;
922 GString *dest = g_string_new("");
923 gchar *deststr = NULL;
925 command_str = get_matchparser_tab_str(action->type);
927 if (command_str == NULL)
930 switch(action->type) {
931 case MATCHACTION_MOVE:
932 case MATCHACTION_COPY:
933 case MATCHACTION_EXECUTE:
934 case MATCHACTION_SET_TAG:
935 case MATCHACTION_UNSET_TAG:
936 quoted_dest = matcher_quote_str(action->destination);
937 g_string_append_printf(dest, "%s \"%s\"", command_str, quoted_dest);
941 case MATCHACTION_DELETE:
942 case MATCHACTION_MARK:
943 case MATCHACTION_UNMARK:
944 case MATCHACTION_LOCK:
945 case MATCHACTION_UNLOCK:
946 case MATCHACTION_MARK_AS_READ:
947 case MATCHACTION_MARK_AS_UNREAD:
948 case MATCHACTION_MARK_AS_SPAM:
949 case MATCHACTION_MARK_AS_HAM:
950 case MATCHACTION_STOP:
951 case MATCHACTION_HIDE:
952 case MATCHACTION_IGNORE:
953 case MATCHACTION_WATCH:
954 case MATCHACTION_CLEAR_TAGS:
955 g_string_append_printf(dest, "%s", command_str);
958 case MATCHACTION_REDIRECT:
959 case MATCHACTION_FORWARD:
960 case MATCHACTION_FORWARD_AS_ATTACHMENT:
961 quoted_dest = matcher_quote_str(action->destination);
962 g_string_append_printf(dest, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
966 case MATCHACTION_COLOR:
967 g_string_append_printf(dest, "%s %d", command_str, action->labelcolor);
970 case MATCHACTION_CHANGE_SCORE:
971 case MATCHACTION_SET_SCORE:
972 g_string_append_printf(dest, "%s %d", command_str, action->score);
975 case MATCHACTION_ADD_TO_ADDRESSBOOK:
976 quoted_header = matcher_quote_str(action->header);
977 quoted_dest = matcher_quote_str(action->destination);
978 g_string_append_printf(dest, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
980 g_free(quoted_header);
987 g_string_free(dest, FALSE);
991 gchar * filteringaction_list_to_string(GSList * action_list)
993 gchar *action_list_str;
997 action_list_str = NULL;
998 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
1000 FilteringAction * action;
1004 action_str = filteringaction_to_string(action);
1006 if (action_list_str != NULL) {
1007 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1008 g_free(action_list_str);
1011 list_str = g_strdup(action_str);
1014 action_list_str = list_str;
1017 return action_list_str;
1020 gchar * filteringprop_to_string(FilteringProp * prop)
1023 gchar *action_list_str;
1024 gchar *filtering_str;
1029 action_list_str = filteringaction_list_to_string(prop->action_list);
1031 if (action_list_str == NULL)
1034 list_str = matcherlist_to_string(prop->matchers);
1036 if (list_str == NULL) {
1037 g_free(action_list_str);
1041 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1042 g_free(action_list_str);
1045 return filtering_str;
1048 static void prefs_filtering_free(GSList * prefs_filtering)
1050 while (prefs_filtering != NULL) {
1051 FilteringProp * filtering = (FilteringProp *)
1052 prefs_filtering->data;
1053 filteringprop_free(filtering);
1054 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1058 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1060 FolderItem *item = node->data;
1062 cm_return_val_if_fail(item, FALSE);
1063 cm_return_val_if_fail(item->prefs, FALSE);
1065 prefs_filtering_free(item->prefs->processing);
1066 item->prefs->processing = NULL;
1071 void prefs_filtering_clear(void)
1075 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1078 folder = (Folder *) cur->data;
1079 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1080 prefs_filtering_free_func, NULL);
1083 prefs_filtering_free(filtering_rules);
1084 filtering_rules = NULL;
1085 prefs_filtering_free(pre_global_processing);
1086 pre_global_processing = NULL;
1087 prefs_filtering_free(post_global_processing);
1088 post_global_processing = NULL;
1091 void prefs_filtering_clear_folder(Folder *folder)
1093 cm_return_if_fail(folder);
1094 cm_return_if_fail(folder->node);
1096 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1097 prefs_filtering_free_func, NULL);
1098 /* FIXME: Note folder settings were changed, where the updates? */
1101 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1102 /* return TRUE if there's at least one per-account filtering rule */
1106 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1107 FilteringProp * filtering = (FilteringProp *) l->data;
1109 if (filtering->enabled && (filtering->account_id != 0)) {