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 #define PREFSBUFSIZE 1024
44 GSList * pre_global_processing = NULL;
45 GSList * post_global_processing = NULL;
46 GSList * filtering_rules = NULL;
48 gboolean debug_filtering_session = FALSE;
50 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
52 #define STRLEN_WITH_CHECK(expr) \
53 strlen_with_check(#expr, __LINE__, expr)
55 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
60 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr?expr:"(null)");
65 FilteringAction * filteringaction_new(int type, int account_id,
67 gint labelcolor, gint score, gchar * header)
69 FilteringAction * action;
71 action = g_new0(FilteringAction, 1);
74 action->account_id = account_id;
76 action->destination = g_strdup(destination);
78 action->destination = NULL;
81 action->header = g_strdup(header);
83 action->header = NULL;
85 action->labelcolor = labelcolor;
86 action->score = score;
90 void filteringaction_free(FilteringAction * action)
92 g_return_if_fail(action);
93 g_free(action->header);
94 g_free(action->destination);
98 FilteringProp * filteringprop_new(gboolean enabled,
101 MatcherList * matchers,
102 GSList * action_list)
104 FilteringProp * filtering;
106 filtering = g_new0(FilteringProp, 1);
107 filtering->enabled = enabled;
108 filtering->name = name ? g_strdup(name): NULL;
109 filtering->account_id = account_id;
110 filtering->matchers = matchers;
111 filtering->action_list = action_list;
116 static FilteringAction * filteringaction_copy(FilteringAction * src)
118 FilteringAction * new;
120 new = g_new0(FilteringAction, 1);
122 new->type = src->type;
123 new->account_id = src->account_id;
124 if (src->destination)
125 new->destination = g_strdup(src->destination);
127 new->destination = NULL;
128 new->labelcolor = src->labelcolor;
129 new->score = src->score;
134 FilteringProp * filteringprop_copy(FilteringProp *src)
139 new = g_new0(FilteringProp, 1);
140 new->matchers = g_new0(MatcherList, 1);
142 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
143 MatcherProp *matcher = (MatcherProp *)tmp->data;
145 new->matchers->matchers = g_slist_append(new->matchers->matchers,
146 matcherprop_copy(matcher));
150 new->matchers->bool_and = src->matchers->bool_and;
152 new->action_list = NULL;
154 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
155 FilteringAction *filtering_action;
157 filtering_action = tmp->data;
159 new->action_list = g_slist_append(new->action_list,
160 filteringaction_copy(filtering_action));
163 new->enabled = src->enabled;
164 new->name = g_strdup(src->name);
169 void filteringprop_free(FilteringProp * prop)
173 g_return_if_fail(prop);
174 matcherlist_free(prop->matchers);
176 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
177 filteringaction_free(tmp->data);
183 void filtering_move_and_copy_msg(MsgInfo *msginfo)
185 GSList *list = g_slist_append(NULL, msginfo);
186 filtering_move_and_copy_msgs(list);
190 /* move and copy messages by batches to be faster on IMAP */
191 void filtering_move_and_copy_msgs(GSList *msgs)
193 GSList *messages = g_slist_copy(msgs);
194 FolderItem *last_item = NULL;
195 gboolean is_copy = FALSE, is_move = FALSE;
196 debug_print("checking %d messages\n", g_slist_length(msgs));
198 GSList *batch = NULL, *cur;
200 for (cur = messages; cur; cur = cur->next) {
201 MsgInfo *info = (MsgInfo *)cur->data;
202 if (last_item == NULL) {
203 last_item = info->to_filter_folder;
205 if (last_item == NULL)
207 if (!is_copy && !is_move) {
210 else if (info->is_move)
214 if (info->to_filter_folder == last_item
215 && info->is_copy == is_copy
216 && info->is_move == is_move) {
217 batch = g_slist_prepend(batch, info);
221 debug_print("no more messages to move/copy\n");
224 debug_print("%d messages to %s in %s\n", found,
225 is_copy ? "copy":"move", last_item->name ? last_item->name:"(noname)");
227 for (cur = batch; cur; cur = cur->next) {
228 MsgInfo *info = (MsgInfo *)cur->data;
229 messages = g_slist_remove(messages, info);
231 batch = g_slist_reverse(batch);
232 if (g_slist_length(batch)) {
233 MsgInfo *info = (MsgInfo *)batch->data;
234 if (is_copy && last_item != info->folder) {
235 folder_item_copy_msgs(last_item, batch);
236 } else if (is_move && last_item != info->folder) {
237 if (folder_item_move_msgs(last_item, batch) < 0)
238 folder_item_move_msgs(
239 folder_get_default_inbox(),
242 /* we don't reference the msginfos, because caller will do */
243 if (prefs_common.real_time_sync)
244 folder_item_synchronise(last_item);
253 /* we don't reference the msginfos, because caller will do */
254 g_slist_free(messages);
258 fitleringaction_apply
259 runs the action on one MsgInfo
260 return value : return TRUE if the action could be applied
263 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
265 FolderItem * dest_folder;
268 PrefsAccount * account;
271 switch(action->type) {
272 case MATCHACTION_MOVE:
274 folder_find_item_from_identifier(action->destination);
276 debug_print("*** folder not found '%s'\n",
277 action->destination ?action->destination :"(null)");
281 /* check if mail is set to copy already,
282 * in which case we have to do it */
283 if (info->is_copy && info->to_filter_folder) {
284 debug_print("should cp and mv !\n");
285 folder_item_copy_msg(info->to_filter_folder, info);
286 info->is_copy = FALSE;
288 /* mark message to be moved */
289 info->is_move = TRUE;
290 info->to_filter_folder = dest_folder;
293 case MATCHACTION_COPY:
295 folder_find_item_from_identifier(action->destination);
298 debug_print("*** folder not found '%s'\n",
299 action->destination ?action->destination :"(null)");
303 /* check if mail is set to copy already,
304 * in which case we have to do it */
305 if (info->is_copy && info->to_filter_folder) {
306 debug_print("should cp and mv !\n");
307 folder_item_copy_msg(info->to_filter_folder, info);
308 info->is_copy = FALSE;
310 /* mark message to be copied */
311 info->is_copy = TRUE;
312 info->to_filter_folder = dest_folder;
315 case MATCHACTION_SET_TAG:
316 case MATCHACTION_UNSET_TAG:
317 val = tags_get_id_for_str(action->destination);
319 debug_print("*** tag '%s' not found\n",
320 action->destination ?action->destination :"(null)");
324 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
327 case MATCHACTION_CLEAR_TAGS:
328 procmsg_msginfo_clear_tags(info);
331 case MATCHACTION_DELETE:
332 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
336 case MATCHACTION_MARK:
337 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
340 case MATCHACTION_UNMARK:
341 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
344 case MATCHACTION_LOCK:
345 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
348 case MATCHACTION_UNLOCK:
349 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
352 case MATCHACTION_MARK_AS_READ:
353 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
356 case MATCHACTION_MARK_AS_UNREAD:
357 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
360 case MATCHACTION_MARK_AS_SPAM:
361 procmsg_spam_learner_learn(info, NULL, TRUE);
362 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
363 if (procmsg_spam_get_folder(info)) {
364 info->is_move = TRUE;
365 info->to_filter_folder = procmsg_spam_get_folder(info);
369 case MATCHACTION_MARK_AS_HAM:
370 procmsg_spam_learner_learn(info, NULL, FALSE);
371 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
374 case MATCHACTION_COLOR:
375 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
376 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
379 case MATCHACTION_FORWARD:
380 case MATCHACTION_FORWARD_AS_ATTACHMENT:
381 account = account_find_from_id(action->account_id);
382 compose = compose_forward(account, info,
383 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
385 compose_entry_append(compose, action->destination,
386 compose->account->protocol == A_NNTP
390 val = compose_send(compose);
392 return val == 0 ? TRUE : FALSE;
394 case MATCHACTION_REDIRECT:
395 account = account_find_from_id(action->account_id);
396 compose = compose_redirect(account, info, TRUE);
397 if (compose->account->protocol == A_NNTP)
400 compose_entry_append(compose, action->destination,
403 val = compose_send(compose);
405 return val == 0 ? TRUE : FALSE;
407 case MATCHACTION_EXECUTE:
408 cmd = matching_build_command(action->destination, info);
417 case MATCHACTION_SET_SCORE:
418 info->score = action->score;
421 case MATCHACTION_CHANGE_SCORE:
422 info->score += action->score;
425 case MATCHACTION_STOP:
428 case MATCHACTION_HIDE:
432 case MATCHACTION_IGNORE:
433 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
436 case MATCHACTION_WATCH:
437 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
440 case MATCHACTION_ADD_TO_ADDRESSBOOK:
442 AddressDataSource *book = NULL;
443 AddressBookFile *abf = NULL;
444 ItemFolder *folder = NULL;
449 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
450 g_warning("addressbook folder not found '%s'\n", action->destination);
454 g_warning("addressbook_peek_folder_exists returned NULL book\n");
458 abf = book->rawDataSource;
461 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
462 header = procheader_parse_header(buf);
464 /* add all addresses that are not already in */
465 if (header && *header->body && (*header->body != '\0')) {
466 GSList *address_list = NULL;
470 if (action->destination == NULL ||
471 strcasecmp(action->destination, _("Any")) == 0 ||
472 *(action->destination) == '\0')
475 path = action->destination;
476 start_address_completion(path);
478 address_list = address_list_append(address_list, header->body);
479 for (walk = address_list; walk != NULL; walk = walk->next) {
480 gchar *stripped_addr = g_strdup(walk->data);
481 extract_address(stripped_addr);
483 if (complete_matches_found(walk->data) == 0) {
484 debug_print("adding address '%s' to addressbook '%s'\n",
485 stripped_addr, action->destination);
486 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
487 g_warning("contact could not been added\n");
491 debug_print("address '%s' already found in addressbook '%s', skipping\n",
492 stripped_addr, action->destination);
494 g_free(stripped_addr);
497 g_slist_free(address_list);
498 end_address_completion();
500 g_warning("header '%s' not set or empty\n", action->header);
502 return (errors == 0);
511 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
514 g_return_val_if_fail(action_list, FALSE);
515 g_return_val_if_fail(info, FALSE);
516 for (p = action_list; p && p->data; p = g_slist_next(p)) {
517 FilteringAction *a = (FilteringAction *) p->data;
518 if (filteringaction_apply(a, info)) {
519 if (filtering_is_final_action(a))
528 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
529 PrefsAccount *ac_prefs)
531 /* this function returns true if a filtering rule applies regarding to its account
532 data and if it does, if the conditions list match.
534 per-account data of a filtering rule is either matched against current account
535 when filtering is done manually, or against the account currently used for
536 retrieving messages when it's an manual or automatic fetching of messages.
537 per-account data match doesn't apply to pre-/post-/folder-processing rules.
539 when filtering messages manually:
540 - either the filtering rule is not account-based and it will be processed
541 - or it's per-account and we check if we HAVE TO match it against the current
542 account (according to user-land settings, per-account rules might have to
543 be skipped, or only the rules that match the current account have to be
544 applied, or all rules will have to be applied regardless to if they are
545 account-based or not)
547 notes about debugging output in that function:
548 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
549 no debug output is done when filtering_debug_level is low
552 gboolean matches = FALSE;
554 if (ac_prefs != NULL) {
555 matches = ((filtering->account_id == 0)
556 || (filtering->account_id == ac_prefs->account_id));
559 if (debug_filtering_session) {
560 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
561 if (filtering->account_id == 0) {
562 log_status_ok(LOG_DEBUG_FILTERING,
563 _("rule is not account-based\n"));
565 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
566 log_status_ok(LOG_DEBUG_FILTERING,
567 _("rule is account-based [id=%d, name='%s'], "
568 "matching the account currently used to retrieve messages\n"),
569 ac_prefs->account_id, ac_prefs->account_name);
575 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
576 log_status_skip(LOG_DEBUG_FILTERING,
577 _("rule is account-based, "
578 "not matching the account currently used to retrieve messages\n"));
580 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
581 PrefsAccount *account = account_find_from_id(filtering->account_id);
583 log_status_skip(LOG_DEBUG_FILTERING,
584 _("rule is account-based [id=%d, name='%s'], "
585 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
586 filtering->account_id, account->account_name,
587 ac_prefs->account_id, ac_prefs->account_name);
593 switch (prefs_common.apply_per_account_filtering_rules) {
594 case FILTERING_ACCOUNT_RULES_FORCE:
595 /* apply filtering rules regardless to the account info */
599 if (debug_filtering_session) {
600 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
601 if (filtering->account_id == 0) {
602 log_status_ok(LOG_DEBUG_FILTERING,
603 _("rule is not account-based, "
604 "all rules are applied on user request anyway\n"));
606 PrefsAccount *account = account_find_from_id(filtering->account_id);
608 log_status_ok(LOG_DEBUG_FILTERING,
609 _("rule is account-based [id=%d, name='%s'], "
610 "but all rules are applied on user request\n"),
611 filtering->account_id, account->account_name);
616 case FILTERING_ACCOUNT_RULES_SKIP:
617 /* don't apply filtering rules that belong to an account */
618 matches = (filtering->account_id == 0);
621 if (debug_filtering_session) {
623 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
624 PrefsAccount *account = account_find_from_id(filtering->account_id);
626 log_status_skip(LOG_DEBUG_FILTERING,
627 _("rule is account-based [id=%d, name='%s'], "
628 "skipped on user request\n"),
629 filtering->account_id, account->account_name);
631 log_status_skip(LOG_DEBUG_FILTERING,
632 _("rule is account-based, "
633 "skipped on user request\n"));
636 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
637 log_status_ok(LOG_DEBUG_FILTERING,
638 _("rule is not account-based\n"));
643 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
644 matches = ((filtering->account_id == 0)
645 || (filtering->account_id == cur_account->account_id));
648 if (debug_filtering_session) {
650 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
651 PrefsAccount *account = account_find_from_id(filtering->account_id);
653 log_status_skip(LOG_DEBUG_FILTERING,
654 _("rule is account-based [id=%d, name='%s'], "
655 "not matching current account [id=%d, name='%s']\n"),
656 filtering->account_id, account->account_name,
657 cur_account->account_id, cur_account->account_name);
659 log_status_skip(LOG_DEBUG_FILTERING,
660 _("rule is account-based, "
661 "not matching current account\n"));
664 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
665 if (filtering->account_id == 0) {
666 log_status_ok(LOG_DEBUG_FILTERING,
667 _("rule is not account-based\n"));
669 PrefsAccount *account = account_find_from_id(filtering->account_id);
671 log_status_ok(LOG_DEBUG_FILTERING,
672 _("rule is account-based [id=%d, name='%s'], "
673 "current account [id=%d, name='%s']\n"),
674 account->account_id, account->account_name,
675 cur_account->account_id, cur_account->account_name);
684 return matches && matcherlist_match(filtering->matchers, info);
688 *\brief Apply a rule on message.
690 *\param filtering List of filtering rules.
691 *\param info Message to apply rules on.
692 *\param final Variable returning TRUE or FALSE if one of the
693 * encountered actions was final.
694 * See also \ref filtering_is_final_action.
696 *\return gboolean TRUE to continue applying rules.
698 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
701 gboolean result = TRUE;
706 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
707 FilteringAction * action;
710 filteringaction_to_string(buf, sizeof buf, action);
711 if (debug_filtering_session)
712 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
714 if (FALSE == (result = filteringaction_apply(action, info))) {
715 if (debug_filtering_session) {
716 if (action->type != MATCHACTION_STOP)
717 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
718 log_print(LOG_DEBUG_FILTERING,
719 _("no further processing after action [ %s ]\n"), buf);
721 g_warning("No further processing after rule %s\n", buf);
724 if (filtering_is_final_action(action)) {
733 *\brief Check if an action is "final", i.e. should break further
736 *\param filtering_action Action to check.
738 *\return gboolean TRUE if \a filtering_action is final.
740 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
742 switch(filtering_action->type) {
743 case MATCHACTION_MOVE:
744 case MATCHACTION_DELETE:
745 case MATCHACTION_STOP:
746 case MATCHACTION_MARK_AS_SPAM:
747 return TRUE; /* MsgInfo invalid for message */
753 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
759 g_return_val_if_fail(info != NULL, TRUE);
761 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
762 FilteringProp * filtering = (FilteringProp *) l->data;
764 if (filtering->enabled) {
765 if (debug_filtering_session) {
766 gchar *buf = filteringprop_to_string(filtering);
767 if (filtering->name && *filtering->name != '\0') {
768 log_print(LOG_DEBUG_FILTERING,
769 _("processing rule '%s' [ %s ]\n"),
770 filtering->name, buf);
772 log_print(LOG_DEBUG_FILTERING,
773 _("processing rule <unnamed> [ %s ]\n"),
778 if (filtering_match_condition(filtering, info, ac_prefs)) {
779 apply_next = filtering_apply_rule(filtering, info, &final);
785 if (debug_filtering_session) {
786 gchar *buf = filteringprop_to_string(filtering);
787 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
788 if (filtering->name && *filtering->name != '\0') {
789 log_status_skip(LOG_DEBUG_FILTERING,
790 _("disabled rule '%s' [ %s ]\n"),
791 filtering->name, buf);
793 log_status_skip(LOG_DEBUG_FILTERING,
794 _("disabled rule <unnamed> [ %s ]\n"),
803 /* put in inbox if a final rule could not be applied, or
804 * the last rule was not a final one. */
805 if ((final && !apply_next) || !final) {
813 *\brief Filter a message against a list of rules.
815 *\param flist List of filter rules.
816 *\param info Message.
818 *\return gboolean TRUE if filter rules handled the message.
820 *\note Returning FALSE means the message was not handled,
821 * and that the calling code should do the default
822 * processing. E.g. \ref inc.c::inc_start moves the
823 * message to the inbox.
825 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
826 FilteringInvocationType context, gchar *extra_info)
830 if (prefs_common.enable_filtering_debug) {
831 gchar *tmp = _("undetermined");
834 case FILTERING_INCORPORATION:
835 tmp = _("incorporation");
836 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
838 case FILTERING_MANUALLY:
840 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
842 case FILTERING_FOLDER_PROCESSING:
843 tmp = _("folder processing");
844 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
846 case FILTERING_PRE_PROCESSING:
847 tmp = _("pre-processing");
848 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
850 case FILTERING_POST_PROCESSING:
851 tmp = _("post-processing");
852 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
855 debug_filtering_session = FALSE;
858 if (debug_filtering_session) {
859 gchar *file = procmsg_get_message_file_path(info);
860 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
862 /* show context info and essential info about the message */
863 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
864 log_print(LOG_DEBUG_FILTERING,
865 _("filtering message (%s%s%s)\n"
866 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
867 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
868 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
869 spc, prefs_common_translated_header_name("From:"), info->from,
870 spc, prefs_common_translated_header_name("To:"), info->to,
871 spc, prefs_common_translated_header_name("Subject:"), info->subject);
873 log_print(LOG_DEBUG_FILTERING,
874 _("filtering message (%s%s%s)\n"
875 "%smessage file: %s\n"),
876 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
883 debug_filtering_session = FALSE;
885 ret = filter_msginfo(flist, info, ac_prefs);
886 debug_filtering_session = FALSE;
890 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
892 const gchar *command_str;
894 gchar * quoted_header;
896 command_str = get_matchparser_tab_str(action->type);
898 if (command_str == NULL)
901 switch(action->type) {
902 case MATCHACTION_MOVE:
903 case MATCHACTION_COPY:
904 case MATCHACTION_EXECUTE:
905 case MATCHACTION_SET_TAG:
906 case MATCHACTION_UNSET_TAG:
907 quoted_dest = matcher_quote_str(action->destination);
908 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
912 case MATCHACTION_DELETE:
913 case MATCHACTION_MARK:
914 case MATCHACTION_UNMARK:
915 case MATCHACTION_LOCK:
916 case MATCHACTION_UNLOCK:
917 case MATCHACTION_MARK_AS_READ:
918 case MATCHACTION_MARK_AS_UNREAD:
919 case MATCHACTION_MARK_AS_SPAM:
920 case MATCHACTION_MARK_AS_HAM:
921 case MATCHACTION_STOP:
922 case MATCHACTION_HIDE:
923 case MATCHACTION_IGNORE:
924 case MATCHACTION_WATCH:
925 case MATCHACTION_CLEAR_TAGS:
926 g_snprintf(dest, destlen, "%s", command_str);
929 case MATCHACTION_REDIRECT:
930 case MATCHACTION_FORWARD:
931 case MATCHACTION_FORWARD_AS_ATTACHMENT:
932 quoted_dest = matcher_quote_str(action->destination);
933 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
937 case MATCHACTION_COLOR:
938 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
941 case MATCHACTION_CHANGE_SCORE:
942 case MATCHACTION_SET_SCORE:
943 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
946 case MATCHACTION_ADD_TO_ADDRESSBOOK:
947 quoted_header = matcher_quote_str(action->header);
948 quoted_dest = matcher_quote_str(action->destination);
949 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
951 g_free(quoted_header);
959 gchar * filteringaction_list_to_string(GSList * action_list)
961 gchar *action_list_str;
966 action_list_str = NULL;
967 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
969 FilteringAction * action;
973 action_str = filteringaction_to_string(buf,
976 if (action_list_str != NULL) {
977 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
978 g_free(action_list_str);
981 list_str = g_strdup(action_str);
983 action_list_str = list_str;
986 return action_list_str;
989 gchar * filteringprop_to_string(FilteringProp * prop)
992 gchar *action_list_str;
993 gchar *filtering_str;
995 action_list_str = filteringaction_list_to_string(prop->action_list);
997 if (action_list_str == NULL)
1000 list_str = matcherlist_to_string(prop->matchers);
1002 if (list_str == NULL) {
1003 g_free(action_list_str);
1007 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1008 g_free(action_list_str);
1011 return filtering_str;
1014 void prefs_filtering_free(GSList * prefs_filtering)
1016 while (prefs_filtering != NULL) {
1017 FilteringProp * filtering = (FilteringProp *)
1018 prefs_filtering->data;
1019 filteringprop_free(filtering);
1020 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1024 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1026 FolderItem *item = node->data;
1028 g_return_val_if_fail(item, FALSE);
1029 g_return_val_if_fail(item->prefs, FALSE);
1031 prefs_filtering_free(item->prefs->processing);
1032 item->prefs->processing = NULL;
1037 void prefs_filtering_clear(void)
1041 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1044 folder = (Folder *) cur->data;
1045 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1046 prefs_filtering_free_func, NULL);
1049 prefs_filtering_free(filtering_rules);
1050 filtering_rules = NULL;
1051 prefs_filtering_free(pre_global_processing);
1052 pre_global_processing = NULL;
1053 prefs_filtering_free(post_global_processing);
1054 post_global_processing = NULL;
1057 void prefs_filtering_clear_folder(Folder *folder)
1059 g_return_if_fail(folder);
1060 g_return_if_fail(folder->node);
1062 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1063 prefs_filtering_free_func, NULL);
1064 /* FIXME: Note folder settings were changed, where the updates? */
1067 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1068 /* return TRUE if there's at least one per-account filtering rule */
1072 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1073 FilteringProp * filtering = (FilteringProp *) l->data;
1075 if (filtering->enabled && (filtering->account_id != 0)) {