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 /* move and copy messages by batches to be faster on IMAP */
184 void filtering_move_and_copy_msgs(GSList *msgs)
186 GSList *messages = g_slist_copy(msgs);
187 FolderItem *last_item = NULL;
188 FiltOp cur_op = IS_NOTHING;
190 debug_print("checking %d messages\n", g_slist_length(msgs));
192 GSList *batch = NULL, *cur;
194 for (cur = messages; cur; cur = cur->next) {
195 MsgInfo *info = (MsgInfo *)cur->data;
196 if (last_item == NULL) {
197 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
198 last_item = info->to_filter_folder;
199 else if (info->filter_op == IS_DELE)
200 last_item = info->folder;
202 if (last_item == NULL)
204 if (cur_op == IS_NOTHING) {
205 if (info->filter_op == IS_COPY)
207 else if (info->filter_op == IS_MOVE)
209 else if (info->filter_op == IS_DELE)
212 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
213 if (info->to_filter_folder == last_item
214 && cur_op == info->filter_op) {
216 batch = g_slist_prepend(batch, info);
218 } else if (info->filter_op == IS_DELE) {
219 if (info->folder == last_item
220 && cur_op == info->filter_op) {
222 batch = g_slist_prepend(batch, info);
227 debug_print("no more messages to move/copy/del\n");
230 debug_print("%d messages to %s in %s\n", found,
231 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
232 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
234 for (cur = batch; cur; cur = cur->next) {
235 MsgInfo *info = (MsgInfo *)cur->data;
236 messages = g_slist_remove(messages, info);
237 info->to_filter_folder = NULL;
238 info->filter_op = IS_NOTHING;
240 batch = g_slist_reverse(batch);
241 if (g_slist_length(batch)) {
242 MsgInfo *info = (MsgInfo *)batch->data;
243 if (cur_op == IS_COPY && last_item != info->folder) {
244 folder_item_copy_msgs(last_item, batch);
245 } else if (cur_op == IS_MOVE && last_item != info->folder) {
246 if (folder_item_move_msgs(last_item, batch) < 0)
247 folder_item_move_msgs(
248 folder_get_default_inbox(),
250 } else if (cur_op == IS_DELE && last_item == info->folder) {
251 folder_item_remove_msgs(last_item, batch);
253 /* we don't reference the msginfos, because caller will do */
254 if (prefs_common.real_time_sync)
255 folder_item_synchronise(last_item);
263 /* we don't reference the msginfos, because caller will do */
264 g_slist_free(messages);
268 fitleringaction_apply
269 runs the action on one MsgInfo
270 return value : return TRUE if the action could be applied
273 #define FLUSH_COPY_IF_NEEDED(info) { \
274 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
275 debug_print("must debatch pending copy\n"); \
276 folder_item_copy_msg(info->to_filter_folder, info); \
277 info->filter_op = IS_NOTHING; \
281 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
283 FolderItem * dest_folder;
286 PrefsAccount * account;
289 switch(action->type) {
290 case MATCHACTION_MOVE:
292 folder_find_item_from_identifier(action->destination);
294 debug_print("*** folder not found '%s'\n",
295 action->destination ?action->destination :"(null)");
299 FLUSH_COPY_IF_NEEDED(info);
300 /* mark message to be moved */
301 info->filter_op = IS_MOVE;
302 info->to_filter_folder = dest_folder;
305 case MATCHACTION_COPY:
307 folder_find_item_from_identifier(action->destination);
310 debug_print("*** folder not found '%s'\n",
311 action->destination ?action->destination :"(null)");
315 FLUSH_COPY_IF_NEEDED(info);
316 /* mark message to be copied */
317 info->filter_op = IS_COPY;
318 info->to_filter_folder = dest_folder;
321 case MATCHACTION_SET_TAG:
322 case MATCHACTION_UNSET_TAG:
323 val = tags_get_id_for_str(action->destination);
325 debug_print("*** tag '%s' not found\n",
326 action->destination ?action->destination :"(null)");
329 FLUSH_COPY_IF_NEEDED(info);
330 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
333 case MATCHACTION_CLEAR_TAGS:
334 FLUSH_COPY_IF_NEEDED(info);
335 procmsg_msginfo_clear_tags(info);
338 case MATCHACTION_DELETE:
339 FLUSH_COPY_IF_NEEDED(info);
340 info->filter_op = IS_DELE;
343 case MATCHACTION_MARK:
344 FLUSH_COPY_IF_NEEDED(info);
345 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
348 case MATCHACTION_UNMARK:
349 FLUSH_COPY_IF_NEEDED(info);
350 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
353 case MATCHACTION_LOCK:
354 FLUSH_COPY_IF_NEEDED(info);
355 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
358 case MATCHACTION_UNLOCK:
359 FLUSH_COPY_IF_NEEDED(info);
360 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
363 case MATCHACTION_MARK_AS_READ:
364 FLUSH_COPY_IF_NEEDED(info);
365 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
368 case MATCHACTION_MARK_AS_UNREAD:
369 FLUSH_COPY_IF_NEEDED(info);
370 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
373 case MATCHACTION_MARK_AS_SPAM:
374 FLUSH_COPY_IF_NEEDED(info);
375 procmsg_spam_learner_learn(info, NULL, TRUE);
376 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
377 if (procmsg_spam_get_folder(info)) {
378 info->filter_op = IS_MOVE;
379 info->to_filter_folder = procmsg_spam_get_folder(info);
383 case MATCHACTION_MARK_AS_HAM:
384 FLUSH_COPY_IF_NEEDED(info);
385 procmsg_spam_learner_learn(info, NULL, FALSE);
386 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
389 case MATCHACTION_COLOR:
390 FLUSH_COPY_IF_NEEDED(info);
391 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
392 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
395 case MATCHACTION_FORWARD:
396 case MATCHACTION_FORWARD_AS_ATTACHMENT:
397 account = account_find_from_id(action->account_id);
398 compose = compose_forward(account, info,
399 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
401 compose_entry_append(compose, action->destination,
402 compose->account->protocol == A_NNTP
406 val = compose_send(compose);
408 return val == 0 ? TRUE : FALSE;
410 case MATCHACTION_REDIRECT:
411 account = account_find_from_id(action->account_id);
412 compose = compose_redirect(account, info, TRUE);
413 if (compose->account->protocol == A_NNTP)
416 compose_entry_append(compose, action->destination,
419 val = compose_send(compose);
421 return val == 0 ? TRUE : FALSE;
423 case MATCHACTION_EXECUTE:
424 cmd = matching_build_command(action->destination, info);
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);
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);
523 return (errors == 0);
532 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
535 g_return_val_if_fail(action_list, FALSE);
536 g_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->account_name);
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 filteringaction_to_string(buf, sizeof buf, 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 g_warning("No further processing after rule %s\n", buf);
745 if (filtering_is_final_action(action)) {
754 *\brief Check if an action is "final", i.e. should break further
757 *\param filtering_action Action to check.
759 *\return gboolean TRUE if \a filtering_action is final.
761 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
763 switch(filtering_action->type) {
764 case MATCHACTION_MOVE:
765 case MATCHACTION_DELETE:
766 case MATCHACTION_STOP:
767 case MATCHACTION_MARK_AS_SPAM:
768 return TRUE; /* MsgInfo invalid for message */
774 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
780 g_return_val_if_fail(info != NULL, TRUE);
782 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
783 FilteringProp * filtering = (FilteringProp *) l->data;
785 if (filtering->enabled) {
786 if (debug_filtering_session) {
787 gchar *buf = filteringprop_to_string(filtering);
788 if (filtering->name && *filtering->name != '\0') {
789 log_print(LOG_DEBUG_FILTERING,
790 _("processing rule '%s' [ %s ]\n"),
791 filtering->name, buf);
793 log_print(LOG_DEBUG_FILTERING,
794 _("processing rule <unnamed> [ %s ]\n"),
800 if (filtering_match_condition(filtering, info, ac_prefs)) {
801 apply_next = filtering_apply_rule(filtering, info, &final);
807 if (debug_filtering_session) {
808 gchar *buf = filteringprop_to_string(filtering);
809 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
810 if (filtering->name && *filtering->name != '\0') {
811 log_status_skip(LOG_DEBUG_FILTERING,
812 _("disabled rule '%s' [ %s ]\n"),
813 filtering->name, buf);
815 log_status_skip(LOG_DEBUG_FILTERING,
816 _("disabled rule <unnamed> [ %s ]\n"),
825 /* put in inbox if a final rule could not be applied, or
826 * the last rule was not a final one. */
827 if ((final && !apply_next) || !final) {
835 *\brief Filter a message against a list of rules.
837 *\param flist List of filter rules.
838 *\param info Message.
840 *\return gboolean TRUE if filter rules handled the message.
842 *\note Returning FALSE means the message was not handled,
843 * and that the calling code should do the default
844 * processing. E.g. \ref inc.c::inc_start moves the
845 * message to the inbox.
847 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
848 FilteringInvocationType context, gchar *extra_info)
852 if (prefs_common.enable_filtering_debug) {
853 gchar *tmp = _("undetermined");
856 case FILTERING_INCORPORATION:
857 tmp = _("incorporation");
858 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
860 case FILTERING_MANUALLY:
862 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
864 case FILTERING_FOLDER_PROCESSING:
865 tmp = _("folder processing");
866 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
868 case FILTERING_PRE_PROCESSING:
869 tmp = _("pre-processing");
870 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
872 case FILTERING_POST_PROCESSING:
873 tmp = _("post-processing");
874 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
877 debug_filtering_session = FALSE;
880 if (debug_filtering_session) {
881 gchar *file = procmsg_get_message_file_path(info);
882 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
884 /* show context info and essential info about the message */
885 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
886 log_print(LOG_DEBUG_FILTERING,
887 _("filtering message (%s%s%s)\n"
888 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
889 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
890 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
891 spc, prefs_common_translated_header_name("From:"), info->from,
892 spc, prefs_common_translated_header_name("To:"), info->to,
893 spc, prefs_common_translated_header_name("Subject:"), info->subject);
895 log_print(LOG_DEBUG_FILTERING,
896 _("filtering message (%s%s%s)\n"
897 "%smessage file: %s\n"),
898 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
905 debug_filtering_session = FALSE;
907 ret = filter_msginfo(flist, info, ac_prefs);
908 debug_filtering_session = FALSE;
912 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
914 const gchar *command_str;
916 gchar * quoted_header;
918 command_str = get_matchparser_tab_str(action->type);
920 if (command_str == NULL)
923 switch(action->type) {
924 case MATCHACTION_MOVE:
925 case MATCHACTION_COPY:
926 case MATCHACTION_EXECUTE:
927 case MATCHACTION_SET_TAG:
928 case MATCHACTION_UNSET_TAG:
929 quoted_dest = matcher_quote_str(action->destination);
930 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
934 case MATCHACTION_DELETE:
935 case MATCHACTION_MARK:
936 case MATCHACTION_UNMARK:
937 case MATCHACTION_LOCK:
938 case MATCHACTION_UNLOCK:
939 case MATCHACTION_MARK_AS_READ:
940 case MATCHACTION_MARK_AS_UNREAD:
941 case MATCHACTION_MARK_AS_SPAM:
942 case MATCHACTION_MARK_AS_HAM:
943 case MATCHACTION_STOP:
944 case MATCHACTION_HIDE:
945 case MATCHACTION_IGNORE:
946 case MATCHACTION_WATCH:
947 case MATCHACTION_CLEAR_TAGS:
948 g_snprintf(dest, destlen, "%s", command_str);
951 case MATCHACTION_REDIRECT:
952 case MATCHACTION_FORWARD:
953 case MATCHACTION_FORWARD_AS_ATTACHMENT:
954 quoted_dest = matcher_quote_str(action->destination);
955 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
959 case MATCHACTION_COLOR:
960 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
963 case MATCHACTION_CHANGE_SCORE:
964 case MATCHACTION_SET_SCORE:
965 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
968 case MATCHACTION_ADD_TO_ADDRESSBOOK:
969 quoted_header = matcher_quote_str(action->header);
970 quoted_dest = matcher_quote_str(action->destination);
971 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
973 g_free(quoted_header);
981 gchar * filteringaction_list_to_string(GSList * action_list)
983 gchar *action_list_str;
988 action_list_str = NULL;
989 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
991 FilteringAction * action;
995 action_str = filteringaction_to_string(buf,
998 if (action_list_str != NULL) {
999 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1000 g_free(action_list_str);
1003 list_str = g_strdup(action_str);
1005 action_list_str = list_str;
1008 return action_list_str;
1011 gchar * filteringprop_to_string(FilteringProp * prop)
1014 gchar *action_list_str;
1015 gchar *filtering_str;
1017 action_list_str = filteringaction_list_to_string(prop->action_list);
1019 if (action_list_str == NULL)
1022 list_str = matcherlist_to_string(prop->matchers);
1024 if (list_str == NULL) {
1025 g_free(action_list_str);
1029 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1030 g_free(action_list_str);
1033 return filtering_str;
1036 static void prefs_filtering_free(GSList * prefs_filtering)
1038 while (prefs_filtering != NULL) {
1039 FilteringProp * filtering = (FilteringProp *)
1040 prefs_filtering->data;
1041 filteringprop_free(filtering);
1042 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1046 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1048 FolderItem *item = node->data;
1050 g_return_val_if_fail(item, FALSE);
1051 g_return_val_if_fail(item->prefs, FALSE);
1053 prefs_filtering_free(item->prefs->processing);
1054 item->prefs->processing = NULL;
1059 void prefs_filtering_clear(void)
1063 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1066 folder = (Folder *) cur->data;
1067 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1068 prefs_filtering_free_func, NULL);
1071 prefs_filtering_free(filtering_rules);
1072 filtering_rules = NULL;
1073 prefs_filtering_free(pre_global_processing);
1074 pre_global_processing = NULL;
1075 prefs_filtering_free(post_global_processing);
1076 post_global_processing = NULL;
1079 void prefs_filtering_clear_folder(Folder *folder)
1081 g_return_if_fail(folder);
1082 g_return_if_fail(folder->node);
1084 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1085 prefs_filtering_free_func, NULL);
1086 /* FIXME: Note folder settings were changed, where the updates? */
1089 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1090 /* return TRUE if there's at least one per-account filtering rule */
1094 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1095 FilteringProp * filtering = (FilteringProp *) l->data;
1097 if (filtering->enabled && (filtering->account_id != 0)) {