2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 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 #ifndef USE_NEW_ADDRBOOK
41 #include "addressbook-dbus.h"
42 #include "addressadd.h"
44 #include "addr_compl.h"
48 GSList * pre_global_processing = NULL;
49 GSList * post_global_processing = NULL;
50 GSList * filtering_rules = NULL;
52 gboolean debug_filtering_session = FALSE;
54 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
56 #define STRLEN_WITH_CHECK(expr) \
57 strlen_with_check(#expr, __LINE__, expr)
59 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
64 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr?expr:"(null)");
69 FilteringAction * filteringaction_new(int type, int account_id,
71 gint labelcolor, gint score, gchar * header)
73 FilteringAction * action;
75 action = g_new0(FilteringAction, 1);
78 action->account_id = account_id;
80 action->destination = g_strdup(destination);
82 action->destination = NULL;
85 action->header = g_strdup(header);
87 action->header = NULL;
89 action->labelcolor = labelcolor;
90 action->score = score;
94 void filteringaction_free(FilteringAction * action)
96 cm_return_if_fail(action);
97 g_free(action->header);
98 g_free(action->destination);
102 static gint action_list_sort(gconstpointer a, gconstpointer b)
104 int first = filtering_is_final_action((FilteringAction *) a) ? 1 : 0;
105 int second = filtering_is_final_action((FilteringAction *) b) ? 1 : 0;
107 return (first - second);
110 GSList *filtering_action_list_sort(GSList *action_list)
112 return g_slist_sort(action_list, action_list_sort);
115 FilteringProp * filteringprop_new(gboolean enabled,
118 MatcherList * matchers,
119 GSList * action_list)
121 FilteringProp * filtering;
123 filtering = g_new0(FilteringProp, 1);
124 filtering->enabled = enabled;
125 filtering->name = name ? g_strdup(name): NULL;
126 filtering->account_id = account_id;
127 filtering->matchers = matchers;
128 filtering->action_list = filtering_action_list_sort(action_list);
133 static FilteringAction * filteringaction_copy(FilteringAction * src)
135 FilteringAction * new;
137 new = g_new0(FilteringAction, 1);
139 new->type = src->type;
140 new->account_id = src->account_id;
141 if (src->destination)
142 new->destination = g_strdup(src->destination);
144 new->destination = NULL;
145 new->labelcolor = src->labelcolor;
146 new->score = src->score;
151 FilteringProp * filteringprop_copy(FilteringProp *src)
156 new = g_new0(FilteringProp, 1);
157 new->matchers = g_new0(MatcherList, 1);
159 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
160 MatcherProp *matcher = (MatcherProp *)tmp->data;
162 new->matchers->matchers = g_slist_append(new->matchers->matchers,
163 matcherprop_copy(matcher));
167 new->matchers->bool_and = src->matchers->bool_and;
169 new->action_list = NULL;
171 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
172 FilteringAction *filtering_action;
174 filtering_action = tmp->data;
176 new->action_list = g_slist_append(new->action_list,
177 filteringaction_copy(filtering_action));
180 new->enabled = src->enabled;
181 new->name = g_strdup(src->name);
186 void filteringprop_free(FilteringProp * prop)
190 cm_return_if_fail(prop);
191 matcherlist_free(prop->matchers);
193 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
194 filteringaction_free(tmp->data);
196 g_slist_free(prop->action_list);
201 /* move and copy messages by batches to be faster on IMAP */
202 void filtering_move_and_copy_msgs(GSList *msgs)
204 GSList *messages = g_slist_copy(msgs);
205 FolderItem *last_item = NULL;
206 FiltOp cur_op = IS_NOTHING;
208 debug_print("checking %d messages\n", g_slist_length(msgs));
210 GSList *batch = NULL, *cur;
212 for (cur = messages; cur; cur = cur->next) {
213 MsgInfo *info = (MsgInfo *)cur->data;
214 if (last_item == NULL) {
215 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
216 last_item = info->to_filter_folder;
217 else if (info->filter_op == IS_DELE)
218 last_item = info->folder;
220 if (last_item == NULL)
222 if (cur_op == IS_NOTHING) {
223 if (info->filter_op == IS_COPY)
225 else if (info->filter_op == IS_MOVE)
227 else if (info->filter_op == IS_DELE)
230 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
231 if (info->to_filter_folder == last_item
232 && cur_op == info->filter_op) {
234 batch = g_slist_prepend(batch, info);
236 } else if (info->filter_op == IS_DELE) {
237 if (info->folder == last_item
238 && cur_op == info->filter_op) {
240 batch = g_slist_prepend(batch, info);
245 debug_print("no more messages to move/copy/del\n");
248 debug_print("%d messages to %s in %s\n", found,
249 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
250 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
252 for (cur = batch; cur; cur = cur->next) {
253 MsgInfo *info = (MsgInfo *)cur->data;
254 messages = g_slist_remove(messages, info);
255 info->to_filter_folder = NULL;
256 info->filter_op = IS_NOTHING;
258 batch = g_slist_reverse(batch);
259 if (g_slist_length(batch)) {
260 MsgInfo *info = (MsgInfo *)batch->data;
261 if (cur_op == IS_COPY && last_item != info->folder) {
262 folder_item_copy_msgs(last_item, batch);
263 } else if (cur_op == IS_MOVE && last_item != info->folder) {
264 if (folder_item_move_msgs(last_item, batch) < 0)
265 folder_item_move_msgs(
266 folder_get_default_inbox(),
268 } else if (cur_op == IS_DELE && last_item == info->folder) {
269 folder_item_remove_msgs(last_item, batch);
271 /* we don't reference the msginfos, because caller will do */
272 if (prefs_common.real_time_sync)
273 folder_item_synchronise(last_item);
281 /* we don't reference the msginfos, because caller will do */
282 g_slist_free(messages);
286 fitleringaction_apply
287 runs the action on one MsgInfo
288 return value : return TRUE if the action could be applied
291 #define FLUSH_COPY_IF_NEEDED(info) { \
292 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
293 debug_print("must debatch pending copy\n"); \
294 folder_item_copy_msg(info->to_filter_folder, info); \
295 info->filter_op = IS_NOTHING; \
299 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
301 FolderItem * dest_folder;
304 PrefsAccount * account;
307 switch(action->type) {
308 case MATCHACTION_MOVE:
309 if (MSG_IS_LOCKED(info->flags))
313 folder_find_item_from_identifier(action->destination);
315 debug_print("*** folder not found '%s'\n",
316 action->destination ?action->destination :"(null)");
320 FLUSH_COPY_IF_NEEDED(info);
321 /* mark message to be moved */
322 info->filter_op = IS_MOVE;
323 info->to_filter_folder = dest_folder;
326 case MATCHACTION_COPY:
328 folder_find_item_from_identifier(action->destination);
331 debug_print("*** folder not found '%s'\n",
332 action->destination ?action->destination :"(null)");
336 FLUSH_COPY_IF_NEEDED(info);
337 /* mark message to be copied */
338 info->filter_op = IS_COPY;
339 info->to_filter_folder = dest_folder;
342 case MATCHACTION_SET_TAG:
343 case MATCHACTION_UNSET_TAG:
344 val = tags_get_id_for_str(action->destination);
346 debug_print("*** tag '%s' not found\n",
347 action->destination ?action->destination :"(null)");
350 FLUSH_COPY_IF_NEEDED(info);
351 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
354 case MATCHACTION_CLEAR_TAGS:
355 FLUSH_COPY_IF_NEEDED(info);
356 procmsg_msginfo_clear_tags(info);
359 case MATCHACTION_DELETE:
360 FLUSH_COPY_IF_NEEDED(info);
361 info->filter_op = IS_DELE;
364 case MATCHACTION_MARK:
365 FLUSH_COPY_IF_NEEDED(info);
366 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
369 case MATCHACTION_UNMARK:
370 FLUSH_COPY_IF_NEEDED(info);
371 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
374 case MATCHACTION_LOCK:
375 FLUSH_COPY_IF_NEEDED(info);
376 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
379 case MATCHACTION_UNLOCK:
380 FLUSH_COPY_IF_NEEDED(info);
381 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
384 case MATCHACTION_MARK_AS_READ:
385 FLUSH_COPY_IF_NEEDED(info);
386 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
389 case MATCHACTION_MARK_AS_UNREAD:
390 FLUSH_COPY_IF_NEEDED(info);
391 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
394 case MATCHACTION_MARK_AS_SPAM:
395 FLUSH_COPY_IF_NEEDED(info);
396 procmsg_spam_learner_learn(info, NULL, TRUE);
397 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
398 if (procmsg_spam_get_folder(info)) {
399 info->filter_op = IS_MOVE;
400 info->to_filter_folder = procmsg_spam_get_folder(info);
404 case MATCHACTION_MARK_AS_HAM:
405 FLUSH_COPY_IF_NEEDED(info);
406 procmsg_spam_learner_learn(info, NULL, FALSE);
407 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
410 case MATCHACTION_COLOR:
411 FLUSH_COPY_IF_NEEDED(info);
412 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
413 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
416 case MATCHACTION_FORWARD:
417 case MATCHACTION_FORWARD_AS_ATTACHMENT:
418 account = account_find_from_id(action->account_id);
419 compose = compose_forward(account, info,
420 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
422 compose_entry_append(compose, action->destination,
423 compose->account->protocol == A_NNTP
425 : COMPOSE_TO, PREF_NONE);
427 val = compose_send(compose);
429 return val == 0 ? TRUE : FALSE;
431 case MATCHACTION_REDIRECT:
432 account = account_find_from_id(action->account_id);
433 compose = compose_redirect(account, info, TRUE);
434 if (compose->account->protocol == A_NNTP)
437 compose_entry_append(compose, action->destination,
438 COMPOSE_TO, PREF_NONE);
440 val = compose_send(compose);
442 return val == 0 ? TRUE : FALSE;
444 case MATCHACTION_EXECUTE:
445 cmd = matching_build_command(action->destination, info);
449 if (system(cmd) == -1)
450 g_warning("couldn't run %s", cmd);
455 case MATCHACTION_SET_SCORE:
456 FLUSH_COPY_IF_NEEDED(info);
457 info->score = action->score;
460 case MATCHACTION_CHANGE_SCORE:
461 FLUSH_COPY_IF_NEEDED(info);
462 info->score += action->score;
465 case MATCHACTION_STOP:
468 case MATCHACTION_HIDE:
469 FLUSH_COPY_IF_NEEDED(info);
473 case MATCHACTION_IGNORE:
474 FLUSH_COPY_IF_NEEDED(info);
475 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
478 case MATCHACTION_WATCH:
479 FLUSH_COPY_IF_NEEDED(info);
480 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
483 case MATCHACTION_ADD_TO_ADDRESSBOOK:
485 #ifndef USE_NEW_ADDRBOOK
486 AddressDataSource *book = NULL;
487 AddressBookFile *abf = NULL;
488 ItemFolder *folder = NULL;
494 #ifndef USE_NEW_ADDRBOOK
495 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
496 g_warning("addressbook folder not found '%s'\n", action->destination?action->destination:"(null)");
500 g_warning("addressbook_peek_folder_exists returned NULL book\n");
504 abf = book->rawDataSource;
507 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
508 header = procheader_parse_header(buf);
510 /* add all addresses that are not already in */
511 if (header && *header->body && (*header->body != '\0')) {
512 GSList *address_list = NULL;
516 if (action->destination == NULL ||
517 strcasecmp(action->destination, "Any") == 0 ||
518 *(action->destination) == '\0')
521 path = action->destination;
522 start_address_completion(path);
524 address_list = address_list_append(address_list, header->body);
525 for (walk = address_list; walk != NULL; walk = walk->next) {
526 gchar *stripped_addr = g_strdup(walk->data);
527 extract_address(stripped_addr);
529 if (complete_matches_found(walk->data) == 0) {
530 debug_print("adding address '%s' to addressbook '%s'\n",
531 stripped_addr, action->destination);
532 #ifndef USE_NEW_ADDRBOOK
533 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
535 if (!addressadd_selection(NULL, stripped_addr, NULL, NULL)) {
537 g_warning("contact could not been added\n");
541 debug_print("address '%s' already found in addressbook '%s', skipping\n",
542 stripped_addr, action->destination);
544 g_free(stripped_addr);
547 g_slist_free(address_list);
548 end_address_completion();
550 g_warning("header '%s' not set or empty\n", action->header?action->header:"(null)");
552 return (errors == 0);
560 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
563 cm_return_val_if_fail(action_list, FALSE);
564 cm_return_val_if_fail(info, FALSE);
565 for (p = action_list; p && p->data; p = g_slist_next(p)) {
566 FilteringAction *a = (FilteringAction *) p->data;
567 if (filteringaction_apply(a, info)) {
568 if (filtering_is_final_action(a))
577 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
578 PrefsAccount *ac_prefs)
580 /* this function returns true if a filtering rule applies regarding to its account
581 data and if it does, if the conditions list match.
583 per-account data of a filtering rule is either matched against current account
584 when filtering is done manually, or against the account currently used for
585 retrieving messages when it's an manual or automatic fetching of messages.
586 per-account data match doesn't apply to pre-/post-/folder-processing rules.
588 when filtering messages manually:
589 - either the filtering rule is not account-based and it will be processed
590 - or it's per-account and we check if we HAVE TO match it against the current
591 account (according to user-land settings, per-account rules might have to
592 be skipped, or only the rules that match the current account have to be
593 applied, or all rules will have to be applied regardless to if they are
594 account-based or not)
596 notes about debugging output in that function:
597 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
598 no debug output is done when filtering_debug_level is low
601 gboolean matches = FALSE;
603 if (ac_prefs != NULL) {
604 matches = ((filtering->account_id == 0)
605 || (filtering->account_id == ac_prefs->account_id));
608 if (debug_filtering_session) {
609 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
610 if (filtering->account_id == 0) {
611 log_status_ok(LOG_DEBUG_FILTERING,
612 _("rule is not account-based\n"));
614 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
615 log_status_ok(LOG_DEBUG_FILTERING,
616 _("rule is account-based [id=%d, name='%s'], "
617 "matching the account currently used to retrieve messages\n"),
618 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
624 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
625 log_status_skip(LOG_DEBUG_FILTERING,
626 _("rule is account-based, "
627 "not matching the account currently used to retrieve messages\n"));
629 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
630 PrefsAccount *account = account_find_from_id(filtering->account_id);
632 log_status_skip(LOG_DEBUG_FILTERING,
633 _("rule is account-based [id=%d, name='%s'], "
634 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
635 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
636 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
642 switch (prefs_common.apply_per_account_filtering_rules) {
643 case FILTERING_ACCOUNT_RULES_FORCE:
644 /* apply filtering rules regardless to the account info */
648 if (debug_filtering_session) {
649 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
650 if (filtering->account_id == 0) {
651 log_status_ok(LOG_DEBUG_FILTERING,
652 _("rule is not account-based, "
653 "all rules are applied on user request anyway\n"));
655 PrefsAccount *account = account_find_from_id(filtering->account_id);
657 log_status_ok(LOG_DEBUG_FILTERING,
658 _("rule is account-based [id=%d, name='%s'], "
659 "but all rules are applied on user request\n"),
660 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
665 case FILTERING_ACCOUNT_RULES_SKIP:
666 /* don't apply filtering rules that belong to an account */
667 matches = (filtering->account_id == 0);
670 if (debug_filtering_session) {
672 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
673 PrefsAccount *account = account_find_from_id(filtering->account_id);
675 log_status_skip(LOG_DEBUG_FILTERING,
676 _("rule is account-based [id=%d, name='%s'], "
677 "skipped on user request\n"),
678 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
680 log_status_skip(LOG_DEBUG_FILTERING,
681 _("rule is account-based, "
682 "skipped on user request\n"));
685 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
686 log_status_ok(LOG_DEBUG_FILTERING,
687 _("rule is not account-based\n"));
692 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
693 matches = ((filtering->account_id == 0)
694 || (filtering->account_id == cur_account->account_id));
697 if (debug_filtering_session) {
699 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
700 PrefsAccount *account = account_find_from_id(filtering->account_id);
702 log_status_skip(LOG_DEBUG_FILTERING,
703 _("rule is account-based [id=%d, name='%s'], "
704 "not matching current account [id=%d, name='%s']\n"),
705 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
706 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
708 log_status_skip(LOG_DEBUG_FILTERING,
709 _("rule is account-based, "
710 "not matching current account\n"));
713 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
714 if (filtering->account_id == 0) {
715 log_status_ok(LOG_DEBUG_FILTERING,
716 _("rule is not account-based\n"));
718 PrefsAccount *account = account_find_from_id(filtering->account_id);
720 log_status_ok(LOG_DEBUG_FILTERING,
721 _("rule is account-based [id=%d, name='%s'], "
722 "current account [id=%d, name='%s']\n"),
723 account->account_id, account?account->account_name:_("NON_EXISTENT"),
724 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
733 return matches && matcherlist_match(filtering->matchers, info);
737 *\brief Apply a rule on message.
739 *\param filtering List of filtering rules.
740 *\param info Message to apply rules on.
741 *\param final Variable returning TRUE or FALSE if one of the
742 * encountered actions was final.
743 * See also \ref filtering_is_final_action.
745 *\return gboolean TRUE to continue applying rules.
747 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
750 gboolean result = TRUE;
755 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
756 FilteringAction * action;
759 buf = filteringaction_to_string(action);
760 if (debug_filtering_session)
761 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
763 if (FALSE == (result = filteringaction_apply(action, info))) {
764 if (debug_filtering_session) {
765 if (action->type != MATCHACTION_STOP)
766 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
767 log_print(LOG_DEBUG_FILTERING,
768 _("no further processing after action [ %s ]\n"), buf);
770 debug_print("No further processing after rule %s\n", buf);
773 if (filtering_is_final_action(action)) {
783 *\brief Check if an action is "final", i.e. should break further
786 *\param filtering_action Action to check.
788 *\return gboolean TRUE if \a filtering_action is final.
790 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
792 switch(filtering_action->type) {
793 case MATCHACTION_MOVE:
794 case MATCHACTION_DELETE:
795 case MATCHACTION_STOP:
796 case MATCHACTION_MARK_AS_SPAM:
797 return TRUE; /* MsgInfo invalid for message */
803 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
809 cm_return_val_if_fail(info != NULL, TRUE);
811 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
812 FilteringProp * filtering = (FilteringProp *) l->data;
814 if (filtering->enabled) {
815 if (debug_filtering_session) {
816 gchar *buf = filteringprop_to_string(filtering);
817 if (filtering->name && *filtering->name != '\0') {
818 log_print(LOG_DEBUG_FILTERING,
819 _("processing rule '%s' [ %s ]\n"),
820 filtering->name, buf);
822 log_print(LOG_DEBUG_FILTERING,
823 _("processing rule <unnamed> [ %s ]\n"),
829 if (filtering_match_condition(filtering, info, ac_prefs)) {
830 apply_next = filtering_apply_rule(filtering, info, &final);
836 if (debug_filtering_session) {
837 gchar *buf = filteringprop_to_string(filtering);
838 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
839 if (filtering->name && *filtering->name != '\0') {
840 log_status_skip(LOG_DEBUG_FILTERING,
841 _("disabled rule '%s' [ %s ]\n"),
842 filtering->name, buf);
844 log_status_skip(LOG_DEBUG_FILTERING,
845 _("disabled rule <unnamed> [ %s ]\n"),
854 /* put in inbox if the last rule was not a final one, or
855 * a final rule could not be applied.
856 * Either of these cases is likely. */
857 if (!final || !apply_next) {
865 *\brief Filter a message against a list of rules.
867 *\param flist List of filter rules.
868 *\param info Message.
870 *\return gboolean TRUE if filter rules handled the message.
872 *\note Returning FALSE means the message was not handled,
873 * and that the calling code should do the default
874 * processing. E.g. \ref inc.c::inc_start moves the
875 * message to the inbox.
877 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
878 FilteringInvocationType context, gchar *extra_info)
882 if (prefs_common.enable_filtering_debug) {
883 gchar *tmp = _("undetermined");
886 case FILTERING_INCORPORATION:
887 tmp = _("incorporation");
888 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
890 case FILTERING_MANUALLY:
892 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
894 case FILTERING_FOLDER_PROCESSING:
895 tmp = _("folder processing");
896 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
898 case FILTERING_PRE_PROCESSING:
899 tmp = _("pre-processing");
900 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
902 case FILTERING_POST_PROCESSING:
903 tmp = _("post-processing");
904 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
907 debug_filtering_session = FALSE;
911 debug_filtering_session = FALSE;
913 if (debug_filtering_session) {
914 gchar *file = procmsg_get_message_file_path(info);
915 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
917 /* show context info and essential info about the message */
918 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
919 log_print(LOG_DEBUG_FILTERING,
920 _("filtering message (%s%s%s)\n"
921 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
922 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
923 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
924 spc, prefs_common_translated_header_name("From:"), info->from,
925 spc, prefs_common_translated_header_name("To:"), info->to,
926 spc, prefs_common_translated_header_name("Subject:"), info->subject);
928 log_print(LOG_DEBUG_FILTERING,
929 _("filtering message (%s%s%s)\n"
930 "%smessage file: %s\n"),
931 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
938 debug_filtering_session = FALSE;
940 ret = filter_msginfo(flist, info, ac_prefs);
941 debug_filtering_session = FALSE;
945 gchar *filteringaction_to_string(FilteringAction *action)
947 const gchar *command_str;
949 gchar * quoted_header;
950 GString *dest = g_string_new("");
951 gchar *deststr = NULL;
953 command_str = get_matchparser_tab_str(action->type);
955 if (command_str == NULL)
958 switch(action->type) {
959 case MATCHACTION_MOVE:
960 case MATCHACTION_COPY:
961 case MATCHACTION_EXECUTE:
962 case MATCHACTION_SET_TAG:
963 case MATCHACTION_UNSET_TAG:
964 quoted_dest = matcher_quote_str(action->destination);
965 g_string_append_printf(dest, "%s \"%s\"", command_str, quoted_dest);
969 case MATCHACTION_DELETE:
970 case MATCHACTION_MARK:
971 case MATCHACTION_UNMARK:
972 case MATCHACTION_LOCK:
973 case MATCHACTION_UNLOCK:
974 case MATCHACTION_MARK_AS_READ:
975 case MATCHACTION_MARK_AS_UNREAD:
976 case MATCHACTION_MARK_AS_SPAM:
977 case MATCHACTION_MARK_AS_HAM:
978 case MATCHACTION_STOP:
979 case MATCHACTION_HIDE:
980 case MATCHACTION_IGNORE:
981 case MATCHACTION_WATCH:
982 case MATCHACTION_CLEAR_TAGS:
983 g_string_append_printf(dest, "%s", command_str);
986 case MATCHACTION_REDIRECT:
987 case MATCHACTION_FORWARD:
988 case MATCHACTION_FORWARD_AS_ATTACHMENT:
989 quoted_dest = matcher_quote_str(action->destination);
990 g_string_append_printf(dest, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
994 case MATCHACTION_COLOR:
995 g_string_append_printf(dest, "%s %d", command_str, action->labelcolor);
998 case MATCHACTION_CHANGE_SCORE:
999 case MATCHACTION_SET_SCORE:
1000 g_string_append_printf(dest, "%s %d", command_str, action->score);
1003 case MATCHACTION_ADD_TO_ADDRESSBOOK:
1004 quoted_header = matcher_quote_str(action->header);
1005 quoted_dest = matcher_quote_str(action->destination);
1006 g_string_append_printf(dest, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
1007 g_free(quoted_dest);
1008 g_free(quoted_header);
1014 deststr = dest->str;
1015 g_string_free(dest, FALSE);
1019 gchar * filteringaction_list_to_string(GSList * action_list)
1021 gchar *action_list_str;
1025 action_list_str = NULL;
1026 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
1028 FilteringAction * action;
1032 action_str = filteringaction_to_string(action);
1034 if (action_list_str != NULL) {
1035 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1036 g_free(action_list_str);
1039 list_str = g_strdup(action_str);
1042 action_list_str = list_str;
1045 return action_list_str;
1048 gchar * filteringprop_to_string(FilteringProp * prop)
1051 gchar *action_list_str;
1052 gchar *filtering_str;
1057 action_list_str = filteringaction_list_to_string(prop->action_list);
1059 if (action_list_str == NULL)
1062 list_str = matcherlist_to_string(prop->matchers);
1064 if (list_str == NULL) {
1065 g_free(action_list_str);
1069 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1070 g_free(action_list_str);
1073 return filtering_str;
1076 static void prefs_filtering_free(GSList * prefs_filtering)
1078 while (prefs_filtering != NULL) {
1079 FilteringProp * filtering = (FilteringProp *)
1080 prefs_filtering->data;
1081 filteringprop_free(filtering);
1082 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1086 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1088 FolderItem *item = node->data;
1090 cm_return_val_if_fail(item, FALSE);
1091 cm_return_val_if_fail(item->prefs, FALSE);
1093 prefs_filtering_free(item->prefs->processing);
1094 item->prefs->processing = NULL;
1099 void prefs_filtering_clear(void)
1103 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1106 folder = (Folder *) cur->data;
1107 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1108 prefs_filtering_free_func, NULL);
1111 prefs_filtering_free(filtering_rules);
1112 filtering_rules = NULL;
1113 prefs_filtering_free(pre_global_processing);
1114 pre_global_processing = NULL;
1115 prefs_filtering_free(post_global_processing);
1116 post_global_processing = NULL;
1119 void prefs_filtering_clear_folder(Folder *folder)
1121 cm_return_if_fail(folder);
1122 cm_return_if_fail(folder->node);
1124 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1125 prefs_filtering_free_func, NULL);
1126 /* FIXME: Note folder settings were changed, where the updates? */
1129 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1130 /* return TRUE if there's at least one per-account filtering rule */
1134 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1135 FilteringProp * filtering = (FilteringProp *) l->data;
1137 if (filtering->enabled && (filtering->account_id != 0)) {
1145 gboolean filtering_action_list_rename_path(GSList *action_list, const gchar *old_path,
1146 const gchar *new_path)
1152 gchar *old_path_with_sep;
1156 GSList * action_cur;
1157 const gchar *separator=G_DIR_SEPARATOR_S;
1158 gboolean matched = FALSE;
1162 oldpathlen = strlen(old_path);
1163 old_path_with_sep = g_strconcat(old_path,separator,NULL);
1165 for(action_cur = action_list ; action_cur != NULL ;
1166 action_cur = action_cur->next) {
1168 FilteringAction *action = action_cur->data;
1170 if (action->type == MATCHACTION_SET_TAG ||
1171 action->type == MATCHACTION_UNSET_TAG)
1173 if (!action->destination)
1176 destlen = strlen(action->destination);
1178 if (destlen > oldpathlen) {
1179 prefixlen = destlen - oldpathlen;
1180 suffix = action->destination + prefixlen;
1182 if (!strncmp(old_path, suffix, oldpathlen)) {
1183 prefix = g_malloc0(prefixlen + 1);
1184 strncpy2(prefix, action->destination, prefixlen);
1186 base = suffix + oldpathlen;
1187 while (*base == G_DIR_SEPARATOR) base++;
1189 dest_path = g_strconcat(prefix, separator,
1192 dest_path = g_strconcat(prefix,
1199 g_free(action->destination);
1200 action->destination = dest_path;
1202 } else { /* for non-leaf folders */
1203 /* compare with trailing slash */
1204 if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
1206 suffix = action->destination + oldpathlen + 1;
1207 dest_path = g_strconcat(new_path, separator,
1209 g_free(action->destination);
1210 action->destination = dest_path;
1215 /* folder-moving a leaf */
1216 if (!strcmp(old_path, action->destination)) {
1217 dest_path = g_strdup(new_path);
1218 g_free(action->destination);
1219 action->destination = dest_path;
1225 g_free(old_path_with_sep);
1227 if (!strcmp(separator, G_DIR_SEPARATOR_S) && !matched) {