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 cm_return_if_fail(action);
91 g_free(action->header);
92 g_free(action->destination);
96 FilteringProp * filteringprop_new(gboolean enabled,
99 MatcherList * matchers,
100 GSList * action_list)
102 FilteringProp * filtering;
104 filtering = g_new0(FilteringProp, 1);
105 filtering->enabled = enabled;
106 filtering->name = name ? g_strdup(name): NULL;
107 filtering->account_id = account_id;
108 filtering->matchers = matchers;
109 filtering->action_list = action_list;
114 static FilteringAction * filteringaction_copy(FilteringAction * src)
116 FilteringAction * new;
118 new = g_new0(FilteringAction, 1);
120 new->type = src->type;
121 new->account_id = src->account_id;
122 if (src->destination)
123 new->destination = g_strdup(src->destination);
125 new->destination = NULL;
126 new->labelcolor = src->labelcolor;
127 new->score = src->score;
132 FilteringProp * filteringprop_copy(FilteringProp *src)
137 new = g_new0(FilteringProp, 1);
138 new->matchers = g_new0(MatcherList, 1);
140 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
141 MatcherProp *matcher = (MatcherProp *)tmp->data;
143 new->matchers->matchers = g_slist_append(new->matchers->matchers,
144 matcherprop_copy(matcher));
148 new->matchers->bool_and = src->matchers->bool_and;
150 new->action_list = NULL;
152 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
153 FilteringAction *filtering_action;
155 filtering_action = tmp->data;
157 new->action_list = g_slist_append(new->action_list,
158 filteringaction_copy(filtering_action));
161 new->enabled = src->enabled;
162 new->name = g_strdup(src->name);
167 void filteringprop_free(FilteringProp * prop)
171 cm_return_if_fail(prop);
172 matcherlist_free(prop->matchers);
174 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
175 filteringaction_free(tmp->data);
177 g_slist_free(prop->action_list);
182 /* move and copy messages by batches to be faster on IMAP */
183 void filtering_move_and_copy_msgs(GSList *msgs)
185 GSList *messages = g_slist_copy(msgs);
186 FolderItem *last_item = NULL;
187 FiltOp cur_op = IS_NOTHING;
189 debug_print("checking %d messages\n", g_slist_length(msgs));
191 GSList *batch = NULL, *cur;
193 for (cur = messages; cur; cur = cur->next) {
194 MsgInfo *info = (MsgInfo *)cur->data;
195 if (last_item == NULL) {
196 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
197 last_item = info->to_filter_folder;
198 else if (info->filter_op == IS_DELE)
199 last_item = info->folder;
201 if (last_item == NULL)
203 if (cur_op == IS_NOTHING) {
204 if (info->filter_op == IS_COPY)
206 else if (info->filter_op == IS_MOVE)
208 else if (info->filter_op == IS_DELE)
211 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
212 if (info->to_filter_folder == last_item
213 && cur_op == info->filter_op) {
215 batch = g_slist_prepend(batch, info);
217 } else if (info->filter_op == IS_DELE) {
218 if (info->folder == last_item
219 && cur_op == info->filter_op) {
221 batch = g_slist_prepend(batch, info);
226 debug_print("no more messages to move/copy/del\n");
229 debug_print("%d messages to %s in %s\n", found,
230 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
231 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
233 for (cur = batch; cur; cur = cur->next) {
234 MsgInfo *info = (MsgInfo *)cur->data;
235 messages = g_slist_remove(messages, info);
236 info->to_filter_folder = NULL;
237 info->filter_op = IS_NOTHING;
239 batch = g_slist_reverse(batch);
240 if (g_slist_length(batch)) {
241 MsgInfo *info = (MsgInfo *)batch->data;
242 if (cur_op == IS_COPY && last_item != info->folder) {
243 folder_item_copy_msgs(last_item, batch);
244 } else if (cur_op == IS_MOVE && last_item != info->folder) {
245 if (folder_item_move_msgs(last_item, batch) < 0)
246 folder_item_move_msgs(
247 folder_get_default_inbox(),
249 } else if (cur_op == IS_DELE && last_item == info->folder) {
250 folder_item_remove_msgs(last_item, batch);
252 /* we don't reference the msginfos, because caller will do */
253 if (prefs_common.real_time_sync)
254 folder_item_synchronise(last_item);
262 /* we don't reference the msginfos, because caller will do */
263 g_slist_free(messages);
267 fitleringaction_apply
268 runs the action on one MsgInfo
269 return value : return TRUE if the action could be applied
272 #define FLUSH_COPY_IF_NEEDED(info) { \
273 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
274 debug_print("must debatch pending copy\n"); \
275 folder_item_copy_msg(info->to_filter_folder, info); \
276 info->filter_op = IS_NOTHING; \
280 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
282 FolderItem * dest_folder;
285 PrefsAccount * account;
288 switch(action->type) {
289 case MATCHACTION_MOVE:
291 folder_find_item_from_identifier(action->destination);
293 debug_print("*** folder not found '%s'\n",
294 action->destination ?action->destination :"(null)");
298 FLUSH_COPY_IF_NEEDED(info);
299 /* mark message to be moved */
300 info->filter_op = IS_MOVE;
301 info->to_filter_folder = dest_folder;
304 case MATCHACTION_COPY:
306 folder_find_item_from_identifier(action->destination);
309 debug_print("*** folder not found '%s'\n",
310 action->destination ?action->destination :"(null)");
314 FLUSH_COPY_IF_NEEDED(info);
315 /* mark message to be copied */
316 info->filter_op = IS_COPY;
317 info->to_filter_folder = dest_folder;
320 case MATCHACTION_SET_TAG:
321 case MATCHACTION_UNSET_TAG:
322 val = tags_get_id_for_str(action->destination);
324 debug_print("*** tag '%s' not found\n",
325 action->destination ?action->destination :"(null)");
328 FLUSH_COPY_IF_NEEDED(info);
329 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
332 case MATCHACTION_CLEAR_TAGS:
333 FLUSH_COPY_IF_NEEDED(info);
334 procmsg_msginfo_clear_tags(info);
337 case MATCHACTION_DELETE:
338 FLUSH_COPY_IF_NEEDED(info);
339 info->filter_op = IS_DELE;
342 case MATCHACTION_MARK:
343 FLUSH_COPY_IF_NEEDED(info);
344 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
347 case MATCHACTION_UNMARK:
348 FLUSH_COPY_IF_NEEDED(info);
349 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
352 case MATCHACTION_LOCK:
353 FLUSH_COPY_IF_NEEDED(info);
354 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
357 case MATCHACTION_UNLOCK:
358 FLUSH_COPY_IF_NEEDED(info);
359 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
362 case MATCHACTION_MARK_AS_READ:
363 FLUSH_COPY_IF_NEEDED(info);
364 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
367 case MATCHACTION_MARK_AS_UNREAD:
368 FLUSH_COPY_IF_NEEDED(info);
369 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
372 case MATCHACTION_MARK_AS_SPAM:
373 FLUSH_COPY_IF_NEEDED(info);
374 procmsg_spam_learner_learn(info, NULL, TRUE);
375 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
376 if (procmsg_spam_get_folder(info)) {
377 info->filter_op = IS_MOVE;
378 info->to_filter_folder = procmsg_spam_get_folder(info);
382 case MATCHACTION_MARK_AS_HAM:
383 FLUSH_COPY_IF_NEEDED(info);
384 procmsg_spam_learner_learn(info, NULL, FALSE);
385 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
388 case MATCHACTION_COLOR:
389 FLUSH_COPY_IF_NEEDED(info);
390 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
391 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
394 case MATCHACTION_FORWARD:
395 case MATCHACTION_FORWARD_AS_ATTACHMENT:
396 account = account_find_from_id(action->account_id);
397 compose = compose_forward(account, info,
398 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
400 compose_entry_append(compose, action->destination,
401 compose->account->protocol == A_NNTP
403 : COMPOSE_TO, PREF_NONE);
405 val = compose_send(compose);
407 return val == 0 ? TRUE : FALSE;
409 case MATCHACTION_REDIRECT:
410 account = account_find_from_id(action->account_id);
411 compose = compose_redirect(account, info, TRUE);
412 if (compose->account->protocol == A_NNTP)
415 compose_entry_append(compose, action->destination,
416 COMPOSE_TO, PREF_NONE);
418 val = compose_send(compose);
420 return val == 0 ? TRUE : FALSE;
422 case MATCHACTION_EXECUTE:
423 cmd = matching_build_command(action->destination, info);
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 cm_return_val_if_fail(action_list, FALSE);
535 cm_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 buf = filteringaction_to_string(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)) {
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 cm_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;
881 debug_filtering_session = FALSE;
883 if (debug_filtering_session) {
884 gchar *file = procmsg_get_message_file_path(info);
885 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
887 /* show context info and essential info about the message */
888 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
889 log_print(LOG_DEBUG_FILTERING,
890 _("filtering message (%s%s%s)\n"
891 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
892 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
893 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
894 spc, prefs_common_translated_header_name("From:"), info->from,
895 spc, prefs_common_translated_header_name("To:"), info->to,
896 spc, prefs_common_translated_header_name("Subject:"), info->subject);
898 log_print(LOG_DEBUG_FILTERING,
899 _("filtering message (%s%s%s)\n"
900 "%smessage file: %s\n"),
901 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
908 debug_filtering_session = FALSE;
910 ret = filter_msginfo(flist, info, ac_prefs);
911 debug_filtering_session = FALSE;
915 gchar *filteringaction_to_string(FilteringAction *action)
917 const gchar *command_str;
919 gchar * quoted_header;
920 GString *dest = g_string_new("");
921 gchar *deststr = NULL;
923 command_str = get_matchparser_tab_str(action->type);
925 if (command_str == NULL)
928 switch(action->type) {
929 case MATCHACTION_MOVE:
930 case MATCHACTION_COPY:
931 case MATCHACTION_EXECUTE:
932 case MATCHACTION_SET_TAG:
933 case MATCHACTION_UNSET_TAG:
934 quoted_dest = matcher_quote_str(action->destination);
935 g_string_append_printf(dest, "%s \"%s\"", command_str, quoted_dest);
939 case MATCHACTION_DELETE:
940 case MATCHACTION_MARK:
941 case MATCHACTION_UNMARK:
942 case MATCHACTION_LOCK:
943 case MATCHACTION_UNLOCK:
944 case MATCHACTION_MARK_AS_READ:
945 case MATCHACTION_MARK_AS_UNREAD:
946 case MATCHACTION_MARK_AS_SPAM:
947 case MATCHACTION_MARK_AS_HAM:
948 case MATCHACTION_STOP:
949 case MATCHACTION_HIDE:
950 case MATCHACTION_IGNORE:
951 case MATCHACTION_WATCH:
952 case MATCHACTION_CLEAR_TAGS:
953 g_string_append_printf(dest, "%s", command_str);
956 case MATCHACTION_REDIRECT:
957 case MATCHACTION_FORWARD:
958 case MATCHACTION_FORWARD_AS_ATTACHMENT:
959 quoted_dest = matcher_quote_str(action->destination);
960 g_string_append_printf(dest, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
964 case MATCHACTION_COLOR:
965 g_string_append_printf(dest, "%s %d", command_str, action->labelcolor);
968 case MATCHACTION_CHANGE_SCORE:
969 case MATCHACTION_SET_SCORE:
970 g_string_append_printf(dest, "%s %d", command_str, action->score);
973 case MATCHACTION_ADD_TO_ADDRESSBOOK:
974 quoted_header = matcher_quote_str(action->header);
975 quoted_dest = matcher_quote_str(action->destination);
976 g_string_append_printf(dest, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
978 g_free(quoted_header);
985 g_string_free(dest, FALSE);
989 gchar * filteringaction_list_to_string(GSList * action_list)
991 gchar *action_list_str;
995 action_list_str = NULL;
996 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
998 FilteringAction * action;
1002 action_str = filteringaction_to_string(action);
1004 if (action_list_str != NULL) {
1005 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1006 g_free(action_list_str);
1009 list_str = g_strdup(action_str);
1012 action_list_str = list_str;
1015 return action_list_str;
1018 gchar * filteringprop_to_string(FilteringProp * prop)
1021 gchar *action_list_str;
1022 gchar *filtering_str;
1027 action_list_str = filteringaction_list_to_string(prop->action_list);
1029 if (action_list_str == NULL)
1032 list_str = matcherlist_to_string(prop->matchers);
1034 if (list_str == NULL) {
1035 g_free(action_list_str);
1039 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1040 g_free(action_list_str);
1043 return filtering_str;
1046 static void prefs_filtering_free(GSList * prefs_filtering)
1048 while (prefs_filtering != NULL) {
1049 FilteringProp * filtering = (FilteringProp *)
1050 prefs_filtering->data;
1051 filteringprop_free(filtering);
1052 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1056 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1058 FolderItem *item = node->data;
1060 cm_return_val_if_fail(item, FALSE);
1061 cm_return_val_if_fail(item->prefs, FALSE);
1063 prefs_filtering_free(item->prefs->processing);
1064 item->prefs->processing = NULL;
1069 void prefs_filtering_clear(void)
1073 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1076 folder = (Folder *) cur->data;
1077 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1078 prefs_filtering_free_func, NULL);
1081 prefs_filtering_free(filtering_rules);
1082 filtering_rules = NULL;
1083 prefs_filtering_free(pre_global_processing);
1084 pre_global_processing = NULL;
1085 prefs_filtering_free(post_global_processing);
1086 post_global_processing = NULL;
1089 void prefs_filtering_clear_folder(Folder *folder)
1091 cm_return_if_fail(folder);
1092 cm_return_if_fail(folder->node);
1094 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1095 prefs_filtering_free_func, NULL);
1096 /* FIXME: Note folder settings were changed, where the updates? */
1099 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1100 /* return TRUE if there's at least one per-account filtering rule */
1104 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1105 FilteringProp * filtering = (FilteringProp *) l->data;
1107 if (filtering->enabled && (filtering->account_id != 0)) {