2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 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 g_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 g_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);
181 /* move and copy messages by batches to be faster on IMAP */
182 void filtering_move_and_copy_msgs(GSList *msgs)
184 GSList *messages = g_slist_copy(msgs);
185 FolderItem *last_item = NULL;
186 FiltOp cur_op = IS_NOTHING;
188 debug_print("checking %d messages\n", g_slist_length(msgs));
190 GSList *batch = NULL, *cur;
192 for (cur = messages; cur; cur = cur->next) {
193 MsgInfo *info = (MsgInfo *)cur->data;
194 if (last_item == NULL) {
195 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
196 last_item = info->to_filter_folder;
197 else if (info->filter_op == IS_DELE)
198 last_item = info->folder;
200 if (last_item == NULL)
202 if (cur_op == IS_NOTHING) {
203 if (info->filter_op == IS_COPY)
205 else if (info->filter_op == IS_MOVE)
207 else if (info->filter_op == IS_DELE)
210 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
211 if (info->to_filter_folder == last_item
212 && cur_op == info->filter_op) {
214 batch = g_slist_prepend(batch, info);
216 } else if (info->filter_op == IS_DELE) {
217 if (info->folder == last_item
218 && cur_op == info->filter_op) {
220 batch = g_slist_prepend(batch, info);
225 debug_print("no more messages to move/copy/del\n");
228 debug_print("%d messages to %s in %s\n", found,
229 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
230 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
232 for (cur = batch; cur; cur = cur->next) {
233 MsgInfo *info = (MsgInfo *)cur->data;
234 messages = g_slist_remove(messages, info);
235 info->to_filter_folder = NULL;
236 info->filter_op = IS_NOTHING;
238 batch = g_slist_reverse(batch);
239 if (g_slist_length(batch)) {
240 MsgInfo *info = (MsgInfo *)batch->data;
241 if (cur_op == IS_COPY && last_item != info->folder) {
242 folder_item_copy_msgs(last_item, batch);
243 } else if (cur_op == IS_MOVE && last_item != info->folder) {
244 if (folder_item_move_msgs(last_item, batch) < 0)
245 folder_item_move_msgs(
246 folder_get_default_inbox(),
248 } else if (cur_op == IS_DELE && last_item == info->folder) {
249 folder_item_remove_msgs(last_item, batch);
251 /* we don't reference the msginfos, because caller will do */
252 if (prefs_common.real_time_sync)
253 folder_item_synchronise(last_item);
261 /* we don't reference the msginfos, because caller will do */
262 g_slist_free(messages);
266 fitleringaction_apply
267 runs the action on one MsgInfo
268 return value : return TRUE if the action could be applied
271 #define FLUSH_COPY_IF_NEEDED(info) { \
272 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
273 debug_print("must debatch pending copy\n"); \
274 folder_item_copy_msg(info->to_filter_folder, info); \
275 info->filter_op = IS_NOTHING; \
279 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
281 FolderItem * dest_folder;
284 PrefsAccount * account;
287 switch(action->type) {
288 case MATCHACTION_MOVE:
290 folder_find_item_from_identifier(action->destination);
292 debug_print("*** folder not found '%s'\n",
293 action->destination ?action->destination :"(null)");
297 FLUSH_COPY_IF_NEEDED(info);
298 /* mark message to be moved */
299 info->filter_op = IS_MOVE;
300 info->to_filter_folder = dest_folder;
303 case MATCHACTION_COPY:
305 folder_find_item_from_identifier(action->destination);
308 debug_print("*** folder not found '%s'\n",
309 action->destination ?action->destination :"(null)");
313 FLUSH_COPY_IF_NEEDED(info);
314 /* mark message to be copied */
315 info->filter_op = IS_COPY;
316 info->to_filter_folder = dest_folder;
319 case MATCHACTION_SET_TAG:
320 case MATCHACTION_UNSET_TAG:
321 val = tags_get_id_for_str(action->destination);
323 debug_print("*** tag '%s' not found\n",
324 action->destination ?action->destination :"(null)");
327 FLUSH_COPY_IF_NEEDED(info);
328 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
331 case MATCHACTION_CLEAR_TAGS:
332 FLUSH_COPY_IF_NEEDED(info);
333 procmsg_msginfo_clear_tags(info);
336 case MATCHACTION_DELETE:
337 FLUSH_COPY_IF_NEEDED(info);
338 info->filter_op = IS_DELE;
341 case MATCHACTION_MARK:
342 FLUSH_COPY_IF_NEEDED(info);
343 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
346 case MATCHACTION_UNMARK:
347 FLUSH_COPY_IF_NEEDED(info);
348 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
351 case MATCHACTION_LOCK:
352 FLUSH_COPY_IF_NEEDED(info);
353 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
356 case MATCHACTION_UNLOCK:
357 FLUSH_COPY_IF_NEEDED(info);
358 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
361 case MATCHACTION_MARK_AS_READ:
362 FLUSH_COPY_IF_NEEDED(info);
363 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
366 case MATCHACTION_MARK_AS_UNREAD:
367 FLUSH_COPY_IF_NEEDED(info);
368 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
371 case MATCHACTION_MARK_AS_SPAM:
372 FLUSH_COPY_IF_NEEDED(info);
373 procmsg_spam_learner_learn(info, NULL, TRUE);
374 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
375 if (procmsg_spam_get_folder(info)) {
376 info->filter_op = IS_MOVE;
377 info->to_filter_folder = procmsg_spam_get_folder(info);
381 case MATCHACTION_MARK_AS_HAM:
382 FLUSH_COPY_IF_NEEDED(info);
383 procmsg_spam_learner_learn(info, NULL, FALSE);
384 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
387 case MATCHACTION_COLOR:
388 FLUSH_COPY_IF_NEEDED(info);
389 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
390 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
393 case MATCHACTION_FORWARD:
394 case MATCHACTION_FORWARD_AS_ATTACHMENT:
395 account = account_find_from_id(action->account_id);
396 compose = compose_forward(account, info,
397 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
399 compose_entry_append(compose, action->destination,
400 compose->account->protocol == A_NNTP
404 val = compose_send(compose);
406 return val == 0 ? TRUE : FALSE;
408 case MATCHACTION_REDIRECT:
409 account = account_find_from_id(action->account_id);
410 compose = compose_redirect(account, info, TRUE);
411 if (compose->account->protocol == A_NNTP)
414 compose_entry_append(compose, action->destination,
417 val = compose_send(compose);
419 return val == 0 ? TRUE : FALSE;
421 case MATCHACTION_EXECUTE:
422 cmd = matching_build_command(action->destination, info);
431 case MATCHACTION_SET_SCORE:
432 FLUSH_COPY_IF_NEEDED(info);
433 info->score = action->score;
436 case MATCHACTION_CHANGE_SCORE:
437 FLUSH_COPY_IF_NEEDED(info);
438 info->score += action->score;
441 case MATCHACTION_STOP:
444 case MATCHACTION_HIDE:
445 FLUSH_COPY_IF_NEEDED(info);
449 case MATCHACTION_IGNORE:
450 FLUSH_COPY_IF_NEEDED(info);
451 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
454 case MATCHACTION_WATCH:
455 FLUSH_COPY_IF_NEEDED(info);
456 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
459 case MATCHACTION_ADD_TO_ADDRESSBOOK:
461 AddressDataSource *book = NULL;
462 AddressBookFile *abf = NULL;
463 ItemFolder *folder = NULL;
468 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
469 g_warning("addressbook folder not found '%s'\n", action->destination?action->destination:"(null)");
473 g_warning("addressbook_peek_folder_exists returned NULL book\n");
477 abf = book->rawDataSource;
480 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
481 header = procheader_parse_header(buf);
483 /* add all addresses that are not already in */
484 if (header && *header->body && (*header->body != '\0')) {
485 GSList *address_list = NULL;
489 if (action->destination == NULL ||
490 strcasecmp(action->destination, "Any") == 0 ||
491 *(action->destination) == '\0')
494 path = action->destination;
495 start_address_completion(path);
497 address_list = address_list_append(address_list, header->body);
498 for (walk = address_list; walk != NULL; walk = walk->next) {
499 gchar *stripped_addr = g_strdup(walk->data);
500 extract_address(stripped_addr);
502 if (complete_matches_found(walk->data) == 0) {
503 debug_print("adding address '%s' to addressbook '%s'\n",
504 stripped_addr, action->destination);
505 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
506 g_warning("contact could not been added\n");
510 debug_print("address '%s' already found in addressbook '%s', skipping\n",
511 stripped_addr, action->destination);
513 g_free(stripped_addr);
516 g_slist_free(address_list);
517 end_address_completion();
519 g_warning("header '%s' not set or empty\n", action->header?action->header:"(null)");
521 return (errors == 0);
530 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
533 g_return_val_if_fail(action_list, FALSE);
534 g_return_val_if_fail(info, FALSE);
535 for (p = action_list; p && p->data; p = g_slist_next(p)) {
536 FilteringAction *a = (FilteringAction *) p->data;
537 if (filteringaction_apply(a, info)) {
538 if (filtering_is_final_action(a))
547 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
548 PrefsAccount *ac_prefs)
550 /* this function returns true if a filtering rule applies regarding to its account
551 data and if it does, if the conditions list match.
553 per-account data of a filtering rule is either matched against current account
554 when filtering is done manually, or against the account currently used for
555 retrieving messages when it's an manual or automatic fetching of messages.
556 per-account data match doesn't apply to pre-/post-/folder-processing rules.
558 when filtering messages manually:
559 - either the filtering rule is not account-based and it will be processed
560 - or it's per-account and we check if we HAVE TO match it against the current
561 account (according to user-land settings, per-account rules might have to
562 be skipped, or only the rules that match the current account have to be
563 applied, or all rules will have to be applied regardless to if they are
564 account-based or not)
566 notes about debugging output in that function:
567 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
568 no debug output is done when filtering_debug_level is low
571 gboolean matches = FALSE;
573 if (ac_prefs != NULL) {
574 matches = ((filtering->account_id == 0)
575 || (filtering->account_id == ac_prefs->account_id));
578 if (debug_filtering_session) {
579 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
580 if (filtering->account_id == 0) {
581 log_status_ok(LOG_DEBUG_FILTERING,
582 _("rule is not account-based\n"));
584 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
585 log_status_ok(LOG_DEBUG_FILTERING,
586 _("rule is account-based [id=%d, name='%s'], "
587 "matching the account currently used to retrieve messages\n"),
588 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
594 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
595 log_status_skip(LOG_DEBUG_FILTERING,
596 _("rule is account-based, "
597 "not matching the account currently used to retrieve messages\n"));
599 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
600 PrefsAccount *account = account_find_from_id(filtering->account_id);
602 log_status_skip(LOG_DEBUG_FILTERING,
603 _("rule is account-based [id=%d, name='%s'], "
604 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
605 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
606 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
612 switch (prefs_common.apply_per_account_filtering_rules) {
613 case FILTERING_ACCOUNT_RULES_FORCE:
614 /* apply filtering rules regardless to the account info */
618 if (debug_filtering_session) {
619 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
620 if (filtering->account_id == 0) {
621 log_status_ok(LOG_DEBUG_FILTERING,
622 _("rule is not account-based, "
623 "all rules are applied on user request anyway\n"));
625 PrefsAccount *account = account_find_from_id(filtering->account_id);
627 log_status_ok(LOG_DEBUG_FILTERING,
628 _("rule is account-based [id=%d, name='%s'], "
629 "but all rules are applied on user request\n"),
630 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
635 case FILTERING_ACCOUNT_RULES_SKIP:
636 /* don't apply filtering rules that belong to an account */
637 matches = (filtering->account_id == 0);
640 if (debug_filtering_session) {
642 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
643 PrefsAccount *account = account_find_from_id(filtering->account_id);
645 log_status_skip(LOG_DEBUG_FILTERING,
646 _("rule is account-based [id=%d, name='%s'], "
647 "skipped on user request\n"),
648 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
650 log_status_skip(LOG_DEBUG_FILTERING,
651 _("rule is account-based, "
652 "skipped on user request\n"));
655 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
656 log_status_ok(LOG_DEBUG_FILTERING,
657 _("rule is not account-based\n"));
662 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
663 matches = ((filtering->account_id == 0)
664 || (filtering->account_id == cur_account->account_id));
667 if (debug_filtering_session) {
669 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
670 PrefsAccount *account = account_find_from_id(filtering->account_id);
672 log_status_skip(LOG_DEBUG_FILTERING,
673 _("rule is account-based [id=%d, name='%s'], "
674 "not matching current account [id=%d, name='%s']\n"),
675 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
676 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
678 log_status_skip(LOG_DEBUG_FILTERING,
679 _("rule is account-based, "
680 "not matching current account\n"));
683 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
684 if (filtering->account_id == 0) {
685 log_status_ok(LOG_DEBUG_FILTERING,
686 _("rule is not account-based\n"));
688 PrefsAccount *account = account_find_from_id(filtering->account_id);
690 log_status_ok(LOG_DEBUG_FILTERING,
691 _("rule is account-based [id=%d, name='%s'], "
692 "current account [id=%d, name='%s']\n"),
693 account->account_id, account?account->account_name:_("NON_EXISTENT"),
694 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
703 return matches && matcherlist_match(filtering->matchers, info);
707 *\brief Apply a rule on message.
709 *\param filtering List of filtering rules.
710 *\param info Message to apply rules on.
711 *\param final Variable returning TRUE or FALSE if one of the
712 * encountered actions was final.
713 * See also \ref filtering_is_final_action.
715 *\return gboolean TRUE to continue applying rules.
717 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
720 gboolean result = TRUE;
725 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
726 FilteringAction * action;
729 filteringaction_to_string(buf, sizeof buf, action);
730 if (debug_filtering_session)
731 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
733 if (FALSE == (result = filteringaction_apply(action, info))) {
734 if (debug_filtering_session) {
735 if (action->type != MATCHACTION_STOP)
736 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
737 log_print(LOG_DEBUG_FILTERING,
738 _("no further processing after action [ %s ]\n"), buf);
740 g_warning("No further processing after rule %s\n", buf);
743 if (filtering_is_final_action(action)) {
752 *\brief Check if an action is "final", i.e. should break further
755 *\param filtering_action Action to check.
757 *\return gboolean TRUE if \a filtering_action is final.
759 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
761 switch(filtering_action->type) {
762 case MATCHACTION_MOVE:
763 case MATCHACTION_DELETE:
764 case MATCHACTION_STOP:
765 case MATCHACTION_MARK_AS_SPAM:
766 return TRUE; /* MsgInfo invalid for message */
772 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
778 g_return_val_if_fail(info != NULL, TRUE);
780 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
781 FilteringProp * filtering = (FilteringProp *) l->data;
783 if (filtering->enabled) {
784 if (debug_filtering_session) {
785 gchar *buf = filteringprop_to_string(filtering);
786 if (filtering->name && *filtering->name != '\0') {
787 log_print(LOG_DEBUG_FILTERING,
788 _("processing rule '%s' [ %s ]\n"),
789 filtering->name, buf);
791 log_print(LOG_DEBUG_FILTERING,
792 _("processing rule <unnamed> [ %s ]\n"),
798 if (filtering_match_condition(filtering, info, ac_prefs)) {
799 apply_next = filtering_apply_rule(filtering, info, &final);
805 if (debug_filtering_session) {
806 gchar *buf = filteringprop_to_string(filtering);
807 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
808 if (filtering->name && *filtering->name != '\0') {
809 log_status_skip(LOG_DEBUG_FILTERING,
810 _("disabled rule '%s' [ %s ]\n"),
811 filtering->name, buf);
813 log_status_skip(LOG_DEBUG_FILTERING,
814 _("disabled rule <unnamed> [ %s ]\n"),
823 /* put in inbox if a final rule could not be applied, or
824 * the last rule was not a final one. */
825 if ((final && !apply_next) || !final) {
833 *\brief Filter a message against a list of rules.
835 *\param flist List of filter rules.
836 *\param info Message.
838 *\return gboolean TRUE if filter rules handled the message.
840 *\note Returning FALSE means the message was not handled,
841 * and that the calling code should do the default
842 * processing. E.g. \ref inc.c::inc_start moves the
843 * message to the inbox.
845 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
846 FilteringInvocationType context, gchar *extra_info)
850 if (prefs_common.enable_filtering_debug) {
851 gchar *tmp = _("undetermined");
854 case FILTERING_INCORPORATION:
855 tmp = _("incorporation");
856 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
858 case FILTERING_MANUALLY:
860 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
862 case FILTERING_FOLDER_PROCESSING:
863 tmp = _("folder processing");
864 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
866 case FILTERING_PRE_PROCESSING:
867 tmp = _("pre-processing");
868 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
870 case FILTERING_POST_PROCESSING:
871 tmp = _("post-processing");
872 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
875 debug_filtering_session = FALSE;
879 debug_filtering_session = FALSE;
881 if (debug_filtering_session) {
882 gchar *file = procmsg_get_message_file_path(info);
883 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
885 /* show context info and essential info about the message */
886 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
887 log_print(LOG_DEBUG_FILTERING,
888 _("filtering message (%s%s%s)\n"
889 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
890 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
891 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
892 spc, prefs_common_translated_header_name("From:"), info->from,
893 spc, prefs_common_translated_header_name("To:"), info->to,
894 spc, prefs_common_translated_header_name("Subject:"), info->subject);
896 log_print(LOG_DEBUG_FILTERING,
897 _("filtering message (%s%s%s)\n"
898 "%smessage file: %s\n"),
899 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
906 debug_filtering_session = FALSE;
908 ret = filter_msginfo(flist, info, ac_prefs);
909 debug_filtering_session = FALSE;
913 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
915 const gchar *command_str;
917 gchar * quoted_header;
919 command_str = get_matchparser_tab_str(action->type);
921 if (command_str == NULL)
924 switch(action->type) {
925 case MATCHACTION_MOVE:
926 case MATCHACTION_COPY:
927 case MATCHACTION_EXECUTE:
928 case MATCHACTION_SET_TAG:
929 case MATCHACTION_UNSET_TAG:
930 quoted_dest = matcher_quote_str(action->destination);
931 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
935 case MATCHACTION_DELETE:
936 case MATCHACTION_MARK:
937 case MATCHACTION_UNMARK:
938 case MATCHACTION_LOCK:
939 case MATCHACTION_UNLOCK:
940 case MATCHACTION_MARK_AS_READ:
941 case MATCHACTION_MARK_AS_UNREAD:
942 case MATCHACTION_MARK_AS_SPAM:
943 case MATCHACTION_MARK_AS_HAM:
944 case MATCHACTION_STOP:
945 case MATCHACTION_HIDE:
946 case MATCHACTION_IGNORE:
947 case MATCHACTION_WATCH:
948 case MATCHACTION_CLEAR_TAGS:
949 g_snprintf(dest, destlen, "%s", command_str);
952 case MATCHACTION_REDIRECT:
953 case MATCHACTION_FORWARD:
954 case MATCHACTION_FORWARD_AS_ATTACHMENT:
955 quoted_dest = matcher_quote_str(action->destination);
956 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
960 case MATCHACTION_COLOR:
961 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
964 case MATCHACTION_CHANGE_SCORE:
965 case MATCHACTION_SET_SCORE:
966 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
969 case MATCHACTION_ADD_TO_ADDRESSBOOK:
970 quoted_header = matcher_quote_str(action->header);
971 quoted_dest = matcher_quote_str(action->destination);
972 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
974 g_free(quoted_header);
982 gchar * filteringaction_list_to_string(GSList * action_list)
984 gchar *action_list_str;
989 action_list_str = NULL;
990 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
992 FilteringAction * action;
996 action_str = filteringaction_to_string(buf,
999 if (action_list_str != NULL) {
1000 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1001 g_free(action_list_str);
1004 list_str = g_strdup(action_str);
1006 action_list_str = list_str;
1009 return action_list_str;
1012 gchar * filteringprop_to_string(FilteringProp * prop)
1015 gchar *action_list_str;
1016 gchar *filtering_str;
1018 action_list_str = filteringaction_list_to_string(prop->action_list);
1020 if (action_list_str == NULL)
1023 list_str = matcherlist_to_string(prop->matchers);
1025 if (list_str == NULL) {
1026 g_free(action_list_str);
1030 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1031 g_free(action_list_str);
1034 return filtering_str;
1037 static void prefs_filtering_free(GSList * prefs_filtering)
1039 while (prefs_filtering != NULL) {
1040 FilteringProp * filtering = (FilteringProp *)
1041 prefs_filtering->data;
1042 filteringprop_free(filtering);
1043 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1047 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1049 FolderItem *item = node->data;
1051 g_return_val_if_fail(item, FALSE);
1052 g_return_val_if_fail(item->prefs, FALSE);
1054 prefs_filtering_free(item->prefs->processing);
1055 item->prefs->processing = NULL;
1060 void prefs_filtering_clear(void)
1064 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1067 folder = (Folder *) cur->data;
1068 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1069 prefs_filtering_free_func, NULL);
1072 prefs_filtering_free(filtering_rules);
1073 filtering_rules = NULL;
1074 prefs_filtering_free(pre_global_processing);
1075 pre_global_processing = NULL;
1076 prefs_filtering_free(post_global_processing);
1077 post_global_processing = NULL;
1080 void prefs_filtering_clear_folder(Folder *folder)
1082 g_return_if_fail(folder);
1083 g_return_if_fail(folder->node);
1085 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1086 prefs_filtering_free_func, NULL);
1087 /* FIXME: Note folder settings were changed, where the updates? */
1090 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1091 /* return TRUE if there's at least one per-account filtering rule */
1095 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1096 FilteringProp * filtering = (FilteringProp *) l->data;
1098 if (filtering->enabled && (filtering->account_id != 0)) {