2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2009 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);
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
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,
418 val = compose_send(compose);
420 return val == 0 ? TRUE : FALSE;
422 case MATCHACTION_EXECUTE:
423 cmd = matching_build_command(action->destination, info);
432 case MATCHACTION_SET_SCORE:
433 FLUSH_COPY_IF_NEEDED(info);
434 info->score = action->score;
437 case MATCHACTION_CHANGE_SCORE:
438 FLUSH_COPY_IF_NEEDED(info);
439 info->score += action->score;
442 case MATCHACTION_STOP:
445 case MATCHACTION_HIDE:
446 FLUSH_COPY_IF_NEEDED(info);
450 case MATCHACTION_IGNORE:
451 FLUSH_COPY_IF_NEEDED(info);
452 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
455 case MATCHACTION_WATCH:
456 FLUSH_COPY_IF_NEEDED(info);
457 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
460 case MATCHACTION_ADD_TO_ADDRESSBOOK:
462 AddressDataSource *book = NULL;
463 AddressBookFile *abf = NULL;
464 ItemFolder *folder = NULL;
469 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
470 g_warning("addressbook folder not found '%s'\n", action->destination?action->destination:"(null)");
474 g_warning("addressbook_peek_folder_exists returned NULL book\n");
478 abf = book->rawDataSource;
481 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
482 header = procheader_parse_header(buf);
484 /* add all addresses that are not already in */
485 if (header && *header->body && (*header->body != '\0')) {
486 GSList *address_list = NULL;
490 if (action->destination == NULL ||
491 strcasecmp(action->destination, "Any") == 0 ||
492 *(action->destination) == '\0')
495 path = action->destination;
496 start_address_completion(path);
498 address_list = address_list_append(address_list, header->body);
499 for (walk = address_list; walk != NULL; walk = walk->next) {
500 gchar *stripped_addr = g_strdup(walk->data);
501 extract_address(stripped_addr);
503 if (complete_matches_found(walk->data) == 0) {
504 debug_print("adding address '%s' to addressbook '%s'\n",
505 stripped_addr, action->destination);
506 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
507 g_warning("contact could not been added\n");
511 debug_print("address '%s' already found in addressbook '%s', skipping\n",
512 stripped_addr, action->destination);
514 g_free(stripped_addr);
517 g_slist_free(address_list);
518 end_address_completion();
520 g_warning("header '%s' not set or empty\n", action->header?action->header:"(null)");
522 return (errors == 0);
531 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
534 g_return_val_if_fail(action_list, FALSE);
535 g_return_val_if_fail(info, FALSE);
536 for (p = action_list; p && p->data; p = g_slist_next(p)) {
537 FilteringAction *a = (FilteringAction *) p->data;
538 if (filteringaction_apply(a, info)) {
539 if (filtering_is_final_action(a))
548 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
549 PrefsAccount *ac_prefs)
551 /* this function returns true if a filtering rule applies regarding to its account
552 data and if it does, if the conditions list match.
554 per-account data of a filtering rule is either matched against current account
555 when filtering is done manually, or against the account currently used for
556 retrieving messages when it's an manual or automatic fetching of messages.
557 per-account data match doesn't apply to pre-/post-/folder-processing rules.
559 when filtering messages manually:
560 - either the filtering rule is not account-based and it will be processed
561 - or it's per-account and we check if we HAVE TO match it against the current
562 account (according to user-land settings, per-account rules might have to
563 be skipped, or only the rules that match the current account have to be
564 applied, or all rules will have to be applied regardless to if they are
565 account-based or not)
567 notes about debugging output in that function:
568 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
569 no debug output is done when filtering_debug_level is low
572 gboolean matches = FALSE;
574 if (ac_prefs != NULL) {
575 matches = ((filtering->account_id == 0)
576 || (filtering->account_id == ac_prefs->account_id));
579 if (debug_filtering_session) {
580 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
581 if (filtering->account_id == 0) {
582 log_status_ok(LOG_DEBUG_FILTERING,
583 _("rule is not account-based\n"));
585 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
586 log_status_ok(LOG_DEBUG_FILTERING,
587 _("rule is account-based [id=%d, name='%s'], "
588 "matching the account currently used to retrieve messages\n"),
589 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
595 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
596 log_status_skip(LOG_DEBUG_FILTERING,
597 _("rule is account-based, "
598 "not matching the account currently used to retrieve messages\n"));
600 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
601 PrefsAccount *account = account_find_from_id(filtering->account_id);
603 log_status_skip(LOG_DEBUG_FILTERING,
604 _("rule is account-based [id=%d, name='%s'], "
605 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
606 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
607 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
613 switch (prefs_common.apply_per_account_filtering_rules) {
614 case FILTERING_ACCOUNT_RULES_FORCE:
615 /* apply filtering rules regardless to the account info */
619 if (debug_filtering_session) {
620 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
621 if (filtering->account_id == 0) {
622 log_status_ok(LOG_DEBUG_FILTERING,
623 _("rule is not account-based, "
624 "all rules are applied on user request anyway\n"));
626 PrefsAccount *account = account_find_from_id(filtering->account_id);
628 log_status_ok(LOG_DEBUG_FILTERING,
629 _("rule is account-based [id=%d, name='%s'], "
630 "but all rules are applied on user request\n"),
631 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
636 case FILTERING_ACCOUNT_RULES_SKIP:
637 /* don't apply filtering rules that belong to an account */
638 matches = (filtering->account_id == 0);
641 if (debug_filtering_session) {
643 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
644 PrefsAccount *account = account_find_from_id(filtering->account_id);
646 log_status_skip(LOG_DEBUG_FILTERING,
647 _("rule is account-based [id=%d, name='%s'], "
648 "skipped on user request\n"),
649 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
651 log_status_skip(LOG_DEBUG_FILTERING,
652 _("rule is account-based, "
653 "skipped on user request\n"));
656 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
657 log_status_ok(LOG_DEBUG_FILTERING,
658 _("rule is not account-based\n"));
663 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
664 matches = ((filtering->account_id == 0)
665 || (filtering->account_id == cur_account->account_id));
668 if (debug_filtering_session) {
670 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
671 PrefsAccount *account = account_find_from_id(filtering->account_id);
673 log_status_skip(LOG_DEBUG_FILTERING,
674 _("rule is account-based [id=%d, name='%s'], "
675 "not matching current account [id=%d, name='%s']\n"),
676 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
677 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
679 log_status_skip(LOG_DEBUG_FILTERING,
680 _("rule is account-based, "
681 "not matching current account\n"));
684 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
685 if (filtering->account_id == 0) {
686 log_status_ok(LOG_DEBUG_FILTERING,
687 _("rule is not account-based\n"));
689 PrefsAccount *account = account_find_from_id(filtering->account_id);
691 log_status_ok(LOG_DEBUG_FILTERING,
692 _("rule is account-based [id=%d, name='%s'], "
693 "current account [id=%d, name='%s']\n"),
694 account->account_id, account?account->account_name:_("NON_EXISTENT"),
695 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
704 return matches && matcherlist_match(filtering->matchers, info);
708 *\brief Apply a rule on message.
710 *\param filtering List of filtering rules.
711 *\param info Message to apply rules on.
712 *\param final Variable returning TRUE or FALSE if one of the
713 * encountered actions was final.
714 * See also \ref filtering_is_final_action.
716 *\return gboolean TRUE to continue applying rules.
718 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
721 gboolean result = TRUE;
726 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
727 FilteringAction * action;
730 filteringaction_to_string(buf, sizeof buf, action);
731 if (debug_filtering_session)
732 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
734 if (FALSE == (result = filteringaction_apply(action, info))) {
735 if (debug_filtering_session) {
736 if (action->type != MATCHACTION_STOP)
737 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
738 log_print(LOG_DEBUG_FILTERING,
739 _("no further processing after action [ %s ]\n"), buf);
741 g_warning("No further processing after rule %s\n", buf);
744 if (filtering_is_final_action(action)) {
753 *\brief Check if an action is "final", i.e. should break further
756 *\param filtering_action Action to check.
758 *\return gboolean TRUE if \a filtering_action is final.
760 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
762 switch(filtering_action->type) {
763 case MATCHACTION_MOVE:
764 case MATCHACTION_DELETE:
765 case MATCHACTION_STOP:
766 case MATCHACTION_MARK_AS_SPAM:
767 return TRUE; /* MsgInfo invalid for message */
773 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
779 g_return_val_if_fail(info != NULL, TRUE);
781 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
782 FilteringProp * filtering = (FilteringProp *) l->data;
784 if (filtering->enabled) {
785 if (debug_filtering_session) {
786 gchar *buf = filteringprop_to_string(filtering);
787 if (filtering->name && *filtering->name != '\0') {
788 log_print(LOG_DEBUG_FILTERING,
789 _("processing rule '%s' [ %s ]\n"),
790 filtering->name, buf);
792 log_print(LOG_DEBUG_FILTERING,
793 _("processing rule <unnamed> [ %s ]\n"),
799 if (filtering_match_condition(filtering, info, ac_prefs)) {
800 apply_next = filtering_apply_rule(filtering, info, &final);
806 if (debug_filtering_session) {
807 gchar *buf = filteringprop_to_string(filtering);
808 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
809 if (filtering->name && *filtering->name != '\0') {
810 log_status_skip(LOG_DEBUG_FILTERING,
811 _("disabled rule '%s' [ %s ]\n"),
812 filtering->name, buf);
814 log_status_skip(LOG_DEBUG_FILTERING,
815 _("disabled rule <unnamed> [ %s ]\n"),
824 /* put in inbox if a final rule could not be applied, or
825 * the last rule was not a final one. */
826 if ((final && !apply_next) || !final) {
834 *\brief Filter a message against a list of rules.
836 *\param flist List of filter rules.
837 *\param info Message.
839 *\return gboolean TRUE if filter rules handled the message.
841 *\note Returning FALSE means the message was not handled,
842 * and that the calling code should do the default
843 * processing. E.g. \ref inc.c::inc_start moves the
844 * message to the inbox.
846 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
847 FilteringInvocationType context, gchar *extra_info)
851 if (prefs_common.enable_filtering_debug) {
852 gchar *tmp = _("undetermined");
855 case FILTERING_INCORPORATION:
856 tmp = _("incorporation");
857 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
859 case FILTERING_MANUALLY:
861 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
863 case FILTERING_FOLDER_PROCESSING:
864 tmp = _("folder processing");
865 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
867 case FILTERING_PRE_PROCESSING:
868 tmp = _("pre-processing");
869 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
871 case FILTERING_POST_PROCESSING:
872 tmp = _("post-processing");
873 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
876 debug_filtering_session = FALSE;
880 debug_filtering_session = FALSE;
882 if (debug_filtering_session) {
883 gchar *file = procmsg_get_message_file_path(info);
884 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
886 /* show context info and essential info about the message */
887 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
888 log_print(LOG_DEBUG_FILTERING,
889 _("filtering message (%s%s%s)\n"
890 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
891 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
892 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
893 spc, prefs_common_translated_header_name("From:"), info->from,
894 spc, prefs_common_translated_header_name("To:"), info->to,
895 spc, prefs_common_translated_header_name("Subject:"), info->subject);
897 log_print(LOG_DEBUG_FILTERING,
898 _("filtering message (%s%s%s)\n"
899 "%smessage file: %s\n"),
900 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
907 debug_filtering_session = FALSE;
909 ret = filter_msginfo(flist, info, ac_prefs);
910 debug_filtering_session = FALSE;
914 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
916 const gchar *command_str;
918 gchar * quoted_header;
920 command_str = get_matchparser_tab_str(action->type);
922 if (command_str == NULL)
925 switch(action->type) {
926 case MATCHACTION_MOVE:
927 case MATCHACTION_COPY:
928 case MATCHACTION_EXECUTE:
929 case MATCHACTION_SET_TAG:
930 case MATCHACTION_UNSET_TAG:
931 quoted_dest = matcher_quote_str(action->destination);
932 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
936 case MATCHACTION_DELETE:
937 case MATCHACTION_MARK:
938 case MATCHACTION_UNMARK:
939 case MATCHACTION_LOCK:
940 case MATCHACTION_UNLOCK:
941 case MATCHACTION_MARK_AS_READ:
942 case MATCHACTION_MARK_AS_UNREAD:
943 case MATCHACTION_MARK_AS_SPAM:
944 case MATCHACTION_MARK_AS_HAM:
945 case MATCHACTION_STOP:
946 case MATCHACTION_HIDE:
947 case MATCHACTION_IGNORE:
948 case MATCHACTION_WATCH:
949 case MATCHACTION_CLEAR_TAGS:
950 g_snprintf(dest, destlen, "%s", command_str);
953 case MATCHACTION_REDIRECT:
954 case MATCHACTION_FORWARD:
955 case MATCHACTION_FORWARD_AS_ATTACHMENT:
956 quoted_dest = matcher_quote_str(action->destination);
957 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
961 case MATCHACTION_COLOR:
962 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
965 case MATCHACTION_CHANGE_SCORE:
966 case MATCHACTION_SET_SCORE:
967 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
970 case MATCHACTION_ADD_TO_ADDRESSBOOK:
971 quoted_header = matcher_quote_str(action->header);
972 quoted_dest = matcher_quote_str(action->destination);
973 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
975 g_free(quoted_header);
983 gchar * filteringaction_list_to_string(GSList * action_list)
985 gchar *action_list_str;
990 action_list_str = NULL;
991 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
993 FilteringAction * action;
997 action_str = filteringaction_to_string(buf,
1000 if (action_list_str != NULL) {
1001 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1002 g_free(action_list_str);
1005 list_str = g_strdup(action_str);
1007 action_list_str = list_str;
1010 return action_list_str;
1013 gchar * filteringprop_to_string(FilteringProp * prop)
1016 gchar *action_list_str;
1017 gchar *filtering_str;
1022 action_list_str = filteringaction_list_to_string(prop->action_list);
1024 if (action_list_str == NULL)
1027 list_str = matcherlist_to_string(prop->matchers);
1029 if (list_str == NULL) {
1030 g_free(action_list_str);
1034 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1035 g_free(action_list_str);
1038 return filtering_str;
1041 static void prefs_filtering_free(GSList * prefs_filtering)
1043 while (prefs_filtering != NULL) {
1044 FilteringProp * filtering = (FilteringProp *)
1045 prefs_filtering->data;
1046 filteringprop_free(filtering);
1047 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1051 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1053 FolderItem *item = node->data;
1055 g_return_val_if_fail(item, FALSE);
1056 g_return_val_if_fail(item->prefs, FALSE);
1058 prefs_filtering_free(item->prefs->processing);
1059 item->prefs->processing = NULL;
1064 void prefs_filtering_clear(void)
1068 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1071 folder = (Folder *) cur->data;
1072 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1073 prefs_filtering_free_func, NULL);
1076 prefs_filtering_free(filtering_rules);
1077 filtering_rules = NULL;
1078 prefs_filtering_free(pre_global_processing);
1079 pre_global_processing = NULL;
1080 prefs_filtering_free(post_global_processing);
1081 post_global_processing = NULL;
1084 void prefs_filtering_clear_folder(Folder *folder)
1086 g_return_if_fail(folder);
1087 g_return_if_fail(folder->node);
1089 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1090 prefs_filtering_free_func, NULL);
1091 /* FIXME: Note folder settings were changed, where the updates? */
1094 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1095 /* return TRUE if there's at least one per-account filtering rule */
1099 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1100 FilteringProp * filtering = (FilteringProp *) l->data;
1102 if (filtering->enabled && (filtering->account_id != 0)) {