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
40 #include "addressbook.h"
42 #include "addressbook-dbus.h"
43 #include "addressadd.h"
45 #include "addr_compl.h"
49 #include "addrindex.h"
50 #include "folder_item_prefs.h"
52 GSList * pre_global_processing = NULL;
53 GSList * post_global_processing = NULL;
54 GSList * filtering_rules = NULL;
56 gboolean debug_filtering_session = FALSE;
58 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
60 #define STRLEN_WITH_CHECK(expr) \
61 strlen_with_check(#expr, __LINE__, expr)
63 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
68 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr?expr:"(null)");
73 FilteringAction * filteringaction_new(int type, int account_id,
75 gint labelcolor, gint score, gchar * header)
77 FilteringAction * action;
79 action = g_new0(FilteringAction, 1);
82 action->account_id = account_id;
84 action->destination = g_strdup(destination);
86 action->destination = NULL;
89 action->header = g_strdup(header);
91 action->header = NULL;
93 action->labelcolor = labelcolor;
94 action->score = score;
98 void filteringaction_free(FilteringAction * action)
100 cm_return_if_fail(action);
101 g_free(action->header);
102 g_free(action->destination);
106 static gint action_list_sort(gconstpointer a, gconstpointer b)
108 int first = filtering_is_final_action((FilteringAction *) a) ? 1 : 0;
109 int second = filtering_is_final_action((FilteringAction *) b) ? 1 : 0;
111 return (first - second);
114 GSList *filtering_action_list_sort(GSList *action_list)
116 return g_slist_sort(action_list, action_list_sort);
119 FilteringProp * filteringprop_new(gboolean enabled,
122 MatcherList * matchers,
123 GSList * action_list)
125 FilteringProp * filtering;
127 filtering = g_new0(FilteringProp, 1);
128 filtering->enabled = enabled;
129 filtering->name = name ? g_strdup(name): NULL;
130 filtering->account_id = account_id;
131 filtering->matchers = matchers;
132 filtering->action_list = filtering_action_list_sort(action_list);
137 static FilteringAction * filteringaction_copy(FilteringAction * src)
139 FilteringAction * new;
141 new = g_new0(FilteringAction, 1);
143 new->type = src->type;
144 new->account_id = src->account_id;
145 if (src->destination)
146 new->destination = g_strdup(src->destination);
148 new->destination = NULL;
149 new->labelcolor = src->labelcolor;
150 new->score = src->score;
155 FilteringProp * filteringprop_copy(FilteringProp *src)
160 new = g_new0(FilteringProp, 1);
161 new->matchers = g_new0(MatcherList, 1);
163 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
164 MatcherProp *matcher = (MatcherProp *)tmp->data;
166 new->matchers->matchers = g_slist_append(new->matchers->matchers,
167 matcherprop_copy(matcher));
171 new->matchers->bool_and = src->matchers->bool_and;
173 new->action_list = NULL;
175 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
176 FilteringAction *filtering_action;
178 filtering_action = tmp->data;
180 new->action_list = g_slist_append(new->action_list,
181 filteringaction_copy(filtering_action));
184 new->enabled = src->enabled;
185 new->name = g_strdup(src->name);
190 void filteringprop_free(FilteringProp * prop)
194 cm_return_if_fail(prop);
195 matcherlist_free(prop->matchers);
197 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
198 filteringaction_free(tmp->data);
200 g_slist_free(prop->action_list);
205 /* move and copy messages by batches to be faster on IMAP */
206 void filtering_move_and_copy_msgs(GSList *msgs)
208 GSList *messages = g_slist_copy(msgs);
209 FolderItem *last_item = NULL;
210 FiltOp cur_op = IS_NOTHING;
212 debug_print("checking %d messages\n", g_slist_length(msgs));
214 GSList *batch = NULL, *cur;
216 for (cur = messages; cur; cur = cur->next) {
217 MsgInfo *info = (MsgInfo *)cur->data;
218 if (last_item == NULL) {
219 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
220 last_item = info->to_filter_folder;
221 else if (info->filter_op == IS_DELE)
222 last_item = info->folder;
224 if (last_item == NULL)
226 if (cur_op == IS_NOTHING) {
227 if (info->filter_op == IS_COPY)
229 else if (info->filter_op == IS_MOVE)
231 else if (info->filter_op == IS_DELE)
234 if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
235 if (info->to_filter_folder == last_item
236 && cur_op == info->filter_op) {
238 batch = g_slist_prepend(batch, info);
240 } else if (info->filter_op == IS_DELE) {
241 if (info->folder == last_item
242 && cur_op == info->filter_op) {
244 batch = g_slist_prepend(batch, info);
249 debug_print("no more messages to move/copy/del\n");
252 debug_print("%d messages to %s in %s\n", found,
253 cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"),
254 last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
256 for (cur = batch; cur; cur = cur->next) {
257 MsgInfo *info = (MsgInfo *)cur->data;
258 messages = g_slist_remove(messages, info);
259 info->to_filter_folder = NULL;
260 info->filter_op = IS_NOTHING;
262 batch = g_slist_reverse(batch);
263 if (g_slist_length(batch)) {
264 MsgInfo *info = (MsgInfo *)batch->data;
265 if (cur_op == IS_COPY && last_item != info->folder) {
266 folder_item_copy_msgs(last_item, batch);
267 } else if (cur_op == IS_MOVE && last_item != info->folder) {
268 if (folder_item_move_msgs(last_item, batch) < 0)
269 folder_item_move_msgs(
270 folder_get_default_inbox(),
272 } else if (cur_op == IS_DELE && last_item == info->folder) {
273 folder_item_remove_msgs(last_item, batch);
275 /* we don't reference the msginfos, because caller will do */
276 if (prefs_common.real_time_sync)
277 folder_item_synchronise(last_item);
285 /* we don't reference the msginfos, because caller will do */
286 g_slist_free(messages);
290 fitleringaction_apply
291 runs the action on one MsgInfo
292 return value : return TRUE if the action could be applied
295 #define FLUSH_COPY_IF_NEEDED(info) { \
296 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
297 debug_print("must debatch pending copy\n"); \
298 folder_item_copy_msg(info->to_filter_folder, info); \
299 info->filter_op = IS_NOTHING; \
303 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
305 FolderItem * dest_folder;
308 PrefsAccount * account;
311 switch(action->type) {
312 case MATCHACTION_MOVE:
313 if (MSG_IS_LOCKED(info->flags))
317 folder_find_item_from_identifier(action->destination);
319 debug_print("*** folder not found '%s'\n",
320 action->destination ?action->destination :"(null)");
324 FLUSH_COPY_IF_NEEDED(info);
325 /* mark message to be moved */
326 info->filter_op = IS_MOVE;
327 info->to_filter_folder = dest_folder;
330 case MATCHACTION_COPY:
332 folder_find_item_from_identifier(action->destination);
335 debug_print("*** folder not found '%s'\n",
336 action->destination ?action->destination :"(null)");
340 FLUSH_COPY_IF_NEEDED(info);
341 /* mark message to be copied */
342 info->filter_op = IS_COPY;
343 info->to_filter_folder = dest_folder;
346 case MATCHACTION_SET_TAG:
347 case MATCHACTION_UNSET_TAG:
348 val = tags_get_id_for_str(action->destination);
350 debug_print("*** tag '%s' not found\n",
351 action->destination ?action->destination :"(null)");
354 FLUSH_COPY_IF_NEEDED(info);
355 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
358 case MATCHACTION_CLEAR_TAGS:
359 FLUSH_COPY_IF_NEEDED(info);
360 procmsg_msginfo_clear_tags(info);
363 case MATCHACTION_DELETE:
364 FLUSH_COPY_IF_NEEDED(info);
365 info->filter_op = IS_DELE;
368 case MATCHACTION_MARK:
369 FLUSH_COPY_IF_NEEDED(info);
370 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
373 case MATCHACTION_UNMARK:
374 FLUSH_COPY_IF_NEEDED(info);
375 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
378 case MATCHACTION_LOCK:
379 FLUSH_COPY_IF_NEEDED(info);
380 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
383 case MATCHACTION_UNLOCK:
384 FLUSH_COPY_IF_NEEDED(info);
385 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
388 case MATCHACTION_MARK_AS_READ:
389 FLUSH_COPY_IF_NEEDED(info);
390 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
393 case MATCHACTION_MARK_AS_UNREAD:
394 FLUSH_COPY_IF_NEEDED(info);
395 procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
398 case MATCHACTION_MARK_AS_SPAM:
399 FLUSH_COPY_IF_NEEDED(info);
400 procmsg_spam_learner_learn(info, NULL, TRUE);
401 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
402 if (procmsg_spam_get_folder(info)) {
403 info->filter_op = IS_MOVE;
404 info->to_filter_folder = procmsg_spam_get_folder(info);
408 case MATCHACTION_MARK_AS_HAM:
409 FLUSH_COPY_IF_NEEDED(info);
410 procmsg_spam_learner_learn(info, NULL, FALSE);
411 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
414 case MATCHACTION_COLOR:
415 FLUSH_COPY_IF_NEEDED(info);
416 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
417 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
420 case MATCHACTION_FORWARD:
421 case MATCHACTION_FORWARD_AS_ATTACHMENT:
422 account = account_find_from_id(action->account_id);
423 compose = compose_forward(account, info,
424 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
426 compose_entry_append(compose, action->destination,
427 compose->account->protocol == A_NNTP
429 : COMPOSE_TO, PREF_NONE);
431 val = compose_send(compose);
433 return val == 0 ? TRUE : FALSE;
435 case MATCHACTION_REDIRECT:
436 account = account_find_from_id(action->account_id);
437 compose = compose_redirect(account, info, TRUE);
438 if (compose->account->protocol == A_NNTP)
441 compose_entry_append(compose, action->destination,
442 COMPOSE_TO, PREF_NONE);
444 val = compose_send(compose);
446 return val == 0 ? TRUE : FALSE;
448 case MATCHACTION_EXECUTE:
449 cmd = matching_build_command(action->destination, info);
453 if (system(cmd) == -1)
454 g_warning("couldn't run %s", cmd);
459 case MATCHACTION_SET_SCORE:
460 FLUSH_COPY_IF_NEEDED(info);
461 info->score = action->score;
464 case MATCHACTION_CHANGE_SCORE:
465 FLUSH_COPY_IF_NEEDED(info);
466 info->score += action->score;
469 case MATCHACTION_STOP:
472 case MATCHACTION_HIDE:
473 FLUSH_COPY_IF_NEEDED(info);
477 case MATCHACTION_IGNORE:
478 FLUSH_COPY_IF_NEEDED(info);
479 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
482 case MATCHACTION_WATCH:
483 FLUSH_COPY_IF_NEEDED(info);
484 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
487 case MATCHACTION_ADD_TO_ADDRESSBOOK:
489 #ifndef USE_NEW_ADDRBOOK
490 AddressDataSource *book = NULL;
491 AddressBookFile *abf = NULL;
492 ItemFolder *folder = NULL;
498 #ifndef USE_NEW_ADDRBOOK
499 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
500 g_warning("addressbook folder not found '%s'\n", action->destination?action->destination:"(null)");
504 g_warning("addressbook_peek_folder_exists returned NULL book\n");
508 abf = book->rawDataSource;
511 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
512 header = procheader_parse_header(buf);
514 /* add all addresses that are not already in */
515 if (header && *header->body && (*header->body != '\0')) {
516 GSList *address_list = NULL;
520 if (action->destination == NULL ||
521 strcasecmp(action->destination, "Any") == 0 ||
522 *(action->destination) == '\0')
525 path = action->destination;
526 start_address_completion(path);
528 address_list = address_list_append(address_list, header->body);
529 for (walk = address_list; walk != NULL; walk = walk->next) {
530 gchar *stripped_addr = g_strdup(walk->data);
531 extract_address(stripped_addr);
533 if (complete_matches_found(walk->data) == 0) {
534 debug_print("adding address '%s' to addressbook '%s'\n",
535 stripped_addr, action->destination);
536 #ifndef USE_NEW_ADDRBOOK
537 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
539 if (!addressadd_selection(NULL, stripped_addr, NULL, NULL)) {
541 g_warning("contact could not been added\n");
545 debug_print("address '%s' already found in addressbook '%s', skipping\n",
546 stripped_addr, action->destination);
548 g_free(stripped_addr);
551 g_slist_free(address_list);
552 end_address_completion();
554 g_warning("header '%s' not set or empty\n", action->header?action->header:"(null)");
556 return (errors == 0);
564 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
567 cm_return_val_if_fail(action_list, FALSE);
568 cm_return_val_if_fail(info, FALSE);
569 for (p = action_list; p && p->data; p = g_slist_next(p)) {
570 FilteringAction *a = (FilteringAction *) p->data;
571 if (filteringaction_apply(a, info)) {
572 if (filtering_is_final_action(a))
581 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
582 PrefsAccount *ac_prefs)
584 /* this function returns true if a filtering rule applies regarding to its account
585 data and if it does, if the conditions list match.
587 per-account data of a filtering rule is either matched against current account
588 when filtering is done manually, or against the account currently used for
589 retrieving messages when it's an manual or automatic fetching of messages.
590 per-account data match doesn't apply to pre-/post-/folder-processing rules.
592 when filtering messages manually:
593 - either the filtering rule is not account-based and it will be processed
594 - or it's per-account and we check if we HAVE TO match it against the current
595 account (according to user-land settings, per-account rules might have to
596 be skipped, or only the rules that match the current account have to be
597 applied, or all rules will have to be applied regardless to if they are
598 account-based or not)
600 notes about debugging output in that function:
601 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
602 no debug output is done when filtering_debug_level is low
605 gboolean matches = FALSE;
607 if (ac_prefs != NULL) {
608 matches = ((filtering->account_id == 0)
609 || (filtering->account_id == ac_prefs->account_id));
612 if (debug_filtering_session) {
613 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
614 if (filtering->account_id == 0) {
615 log_status_ok(LOG_DEBUG_FILTERING,
616 _("rule is not account-based\n"));
618 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
619 log_status_ok(LOG_DEBUG_FILTERING,
620 _("rule is account-based [id=%d, name='%s'], "
621 "matching the account currently used to retrieve messages\n"),
622 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
628 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
629 log_status_skip(LOG_DEBUG_FILTERING,
630 _("rule is account-based, "
631 "not matching the account currently used to retrieve messages\n"));
633 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
634 PrefsAccount *account = account_find_from_id(filtering->account_id);
636 log_status_skip(LOG_DEBUG_FILTERING,
637 _("rule is account-based [id=%d, name='%s'], "
638 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
639 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
640 ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
646 switch (prefs_common.apply_per_account_filtering_rules) {
647 case FILTERING_ACCOUNT_RULES_FORCE:
648 /* apply filtering rules regardless to the account info */
652 if (debug_filtering_session) {
653 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
654 if (filtering->account_id == 0) {
655 log_status_ok(LOG_DEBUG_FILTERING,
656 _("rule is not account-based, "
657 "all rules are applied on user request anyway\n"));
659 PrefsAccount *account = account_find_from_id(filtering->account_id);
661 log_status_ok(LOG_DEBUG_FILTERING,
662 _("rule is account-based [id=%d, name='%s'], "
663 "but all rules are applied on user request\n"),
664 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
669 case FILTERING_ACCOUNT_RULES_SKIP:
670 /* don't apply filtering rules that belong to an account */
671 matches = (filtering->account_id == 0);
674 if (debug_filtering_session) {
676 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
677 PrefsAccount *account = account_find_from_id(filtering->account_id);
679 log_status_skip(LOG_DEBUG_FILTERING,
680 _("rule is account-based [id=%d, name='%s'], "
681 "skipped on user request\n"),
682 filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
684 log_status_skip(LOG_DEBUG_FILTERING,
685 _("rule is account-based, "
686 "skipped on user request\n"));
689 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
690 log_status_ok(LOG_DEBUG_FILTERING,
691 _("rule is not account-based\n"));
696 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
697 matches = ((filtering->account_id == 0)
698 || (filtering->account_id == cur_account->account_id));
701 if (debug_filtering_session) {
703 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
704 PrefsAccount *account = account_find_from_id(filtering->account_id);
706 log_status_skip(LOG_DEBUG_FILTERING,
707 _("rule is account-based [id=%d, name='%s'], "
708 "not matching current account [id=%d, name='%s']\n"),
709 filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
710 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
712 log_status_skip(LOG_DEBUG_FILTERING,
713 _("rule is account-based, "
714 "not matching current account\n"));
717 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
718 if (filtering->account_id == 0) {
719 log_status_ok(LOG_DEBUG_FILTERING,
720 _("rule is not account-based\n"));
722 PrefsAccount *account = account_find_from_id(filtering->account_id);
724 log_status_ok(LOG_DEBUG_FILTERING,
725 _("rule is account-based [id=%d, name='%s'], "
726 "current account [id=%d, name='%s']\n"),
727 account->account_id, account?account->account_name:_("NON_EXISTENT"),
728 cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
737 return matches && matcherlist_match(filtering->matchers, info);
741 *\brief Apply a rule on message.
743 *\param filtering List of filtering rules.
744 *\param info Message to apply rules on.
745 *\param final Variable returning TRUE or FALSE if one of the
746 * encountered actions was final.
747 * See also \ref filtering_is_final_action.
749 *\return gboolean TRUE to continue applying rules.
751 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
754 gboolean result = TRUE;
759 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
760 FilteringAction * action;
763 buf = filteringaction_to_string(action);
764 if (debug_filtering_session)
765 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
767 if (FALSE == (result = filteringaction_apply(action, info))) {
768 if (debug_filtering_session) {
769 if (action->type != MATCHACTION_STOP)
770 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
771 log_print(LOG_DEBUG_FILTERING,
772 _("no further processing after action [ %s ]\n"), buf);
774 debug_print("No further processing after rule %s\n", buf);
777 if (filtering_is_final_action(action)) {
787 *\brief Check if an action is "final", i.e. should break further
790 *\param filtering_action Action to check.
792 *\return gboolean TRUE if \a filtering_action is final.
794 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
796 switch(filtering_action->type) {
797 case MATCHACTION_MOVE:
798 case MATCHACTION_DELETE:
799 case MATCHACTION_STOP:
800 case MATCHACTION_MARK_AS_SPAM:
801 return TRUE; /* MsgInfo invalid for message */
807 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
813 cm_return_val_if_fail(info != NULL, TRUE);
815 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
816 FilteringProp * filtering = (FilteringProp *) l->data;
818 if (filtering->enabled) {
819 if (debug_filtering_session) {
820 gchar *buf = filteringprop_to_string(filtering);
821 if (filtering->name && *filtering->name != '\0') {
822 log_print(LOG_DEBUG_FILTERING,
823 _("processing rule '%s' [ %s ]\n"),
824 filtering->name, buf);
826 log_print(LOG_DEBUG_FILTERING,
827 _("processing rule <unnamed> [ %s ]\n"),
833 if (filtering_match_condition(filtering, info, ac_prefs)) {
834 apply_next = filtering_apply_rule(filtering, info, &final);
840 if (debug_filtering_session) {
841 gchar *buf = filteringprop_to_string(filtering);
842 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
843 if (filtering->name && *filtering->name != '\0') {
844 log_status_skip(LOG_DEBUG_FILTERING,
845 _("disabled rule '%s' [ %s ]\n"),
846 filtering->name, buf);
848 log_status_skip(LOG_DEBUG_FILTERING,
849 _("disabled rule <unnamed> [ %s ]\n"),
858 /* put in inbox if the last rule was not a final one, or
859 * a final rule could not be applied.
860 * Either of these cases is likely. */
861 if (!final || !apply_next) {
869 *\brief Filter a message against a list of rules.
871 *\param flist List of filter rules.
872 *\param info Message.
874 *\return gboolean TRUE if filter rules handled the message.
876 *\note Returning FALSE means the message was not handled,
877 * and that the calling code should do the default
878 * processing. E.g. \ref inc.c::inc_start moves the
879 * message to the inbox.
881 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
882 FilteringInvocationType context, gchar *extra_info)
886 if (prefs_common.enable_filtering_debug) {
887 gchar *tmp = _("undetermined");
890 case FILTERING_INCORPORATION:
891 tmp = _("incorporation");
892 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
894 case FILTERING_MANUALLY:
896 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
898 case FILTERING_FOLDER_PROCESSING:
899 tmp = _("folder processing");
900 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
902 case FILTERING_PRE_PROCESSING:
903 tmp = _("pre-processing");
904 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
906 case FILTERING_POST_PROCESSING:
907 tmp = _("post-processing");
908 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
911 debug_filtering_session = FALSE;
915 debug_filtering_session = FALSE;
917 if (debug_filtering_session) {
918 gchar *file = procmsg_get_message_file_path(info);
919 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
921 /* show context info and essential info about the message */
922 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
923 log_print(LOG_DEBUG_FILTERING,
924 _("filtering message (%s%s%s)\n"
925 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
926 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
927 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
928 spc, prefs_common_translated_header_name("From:"), info->from,
929 spc, prefs_common_translated_header_name("To:"), info->to,
930 spc, prefs_common_translated_header_name("Subject:"), info->subject);
932 log_print(LOG_DEBUG_FILTERING,
933 _("filtering message (%s%s%s)\n"
934 "%smessage file: %s\n"),
935 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
942 debug_filtering_session = FALSE;
944 ret = filter_msginfo(flist, info, ac_prefs);
945 debug_filtering_session = FALSE;
949 gchar *filteringaction_to_string(FilteringAction *action)
951 const gchar *command_str;
953 gchar * quoted_header;
954 GString *dest = g_string_new("");
955 gchar *deststr = NULL;
957 command_str = get_matchparser_tab_str(action->type);
959 if (command_str == NULL)
962 switch(action->type) {
963 case MATCHACTION_MOVE:
964 case MATCHACTION_COPY:
965 case MATCHACTION_EXECUTE:
966 case MATCHACTION_SET_TAG:
967 case MATCHACTION_UNSET_TAG:
968 quoted_dest = matcher_quote_str(action->destination);
969 g_string_append_printf(dest, "%s \"%s\"", command_str, quoted_dest);
973 case MATCHACTION_DELETE:
974 case MATCHACTION_MARK:
975 case MATCHACTION_UNMARK:
976 case MATCHACTION_LOCK:
977 case MATCHACTION_UNLOCK:
978 case MATCHACTION_MARK_AS_READ:
979 case MATCHACTION_MARK_AS_UNREAD:
980 case MATCHACTION_MARK_AS_SPAM:
981 case MATCHACTION_MARK_AS_HAM:
982 case MATCHACTION_STOP:
983 case MATCHACTION_HIDE:
984 case MATCHACTION_IGNORE:
985 case MATCHACTION_WATCH:
986 case MATCHACTION_CLEAR_TAGS:
987 g_string_append_printf(dest, "%s", command_str);
990 case MATCHACTION_REDIRECT:
991 case MATCHACTION_FORWARD:
992 case MATCHACTION_FORWARD_AS_ATTACHMENT:
993 quoted_dest = matcher_quote_str(action->destination);
994 g_string_append_printf(dest, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
998 case MATCHACTION_COLOR:
999 g_string_append_printf(dest, "%s %d", command_str, action->labelcolor);
1002 case MATCHACTION_CHANGE_SCORE:
1003 case MATCHACTION_SET_SCORE:
1004 g_string_append_printf(dest, "%s %d", command_str, action->score);
1007 case MATCHACTION_ADD_TO_ADDRESSBOOK:
1008 quoted_header = matcher_quote_str(action->header);
1009 quoted_dest = matcher_quote_str(action->destination);
1010 g_string_append_printf(dest, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
1011 g_free(quoted_dest);
1012 g_free(quoted_header);
1018 deststr = dest->str;
1019 g_string_free(dest, FALSE);
1023 gchar * filteringaction_list_to_string(GSList * action_list)
1025 gchar *action_list_str;
1029 action_list_str = NULL;
1030 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
1032 FilteringAction * action;
1036 action_str = filteringaction_to_string(action);
1038 if (action_list_str != NULL) {
1039 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
1040 g_free(action_list_str);
1043 list_str = g_strdup(action_str);
1046 action_list_str = list_str;
1049 return action_list_str;
1052 gchar * filteringprop_to_string(FilteringProp * prop)
1055 gchar *action_list_str;
1056 gchar *filtering_str;
1061 action_list_str = filteringaction_list_to_string(prop->action_list);
1063 if (action_list_str == NULL)
1066 list_str = matcherlist_to_string(prop->matchers);
1068 if (list_str == NULL) {
1069 g_free(action_list_str);
1073 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1074 g_free(action_list_str);
1077 return filtering_str;
1080 static void prefs_filtering_free(GSList * prefs_filtering)
1082 while (prefs_filtering != NULL) {
1083 FilteringProp * filtering = (FilteringProp *)
1084 prefs_filtering->data;
1085 filteringprop_free(filtering);
1086 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1090 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1092 FolderItem *item = node->data;
1094 cm_return_val_if_fail(item, FALSE);
1095 cm_return_val_if_fail(item->prefs, FALSE);
1097 prefs_filtering_free(item->prefs->processing);
1098 item->prefs->processing = NULL;
1103 void prefs_filtering_clear(void)
1107 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1110 folder = (Folder *) cur->data;
1111 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1112 prefs_filtering_free_func, NULL);
1115 prefs_filtering_free(filtering_rules);
1116 filtering_rules = NULL;
1117 prefs_filtering_free(pre_global_processing);
1118 pre_global_processing = NULL;
1119 prefs_filtering_free(post_global_processing);
1120 post_global_processing = NULL;
1123 void prefs_filtering_clear_folder(Folder *folder)
1125 cm_return_if_fail(folder);
1126 cm_return_if_fail(folder->node);
1128 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1129 prefs_filtering_free_func, NULL);
1130 /* FIXME: Note folder settings were changed, where the updates? */
1133 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1134 /* return TRUE if there's at least one per-account filtering rule */
1138 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1139 FilteringProp * filtering = (FilteringProp *) l->data;
1141 if (filtering->enabled && (filtering->account_id != 0)) {
1149 gboolean filtering_action_list_rename_path(GSList *action_list, const gchar *old_path,
1150 const gchar *new_path)
1156 gchar *old_path_with_sep;
1160 GSList * action_cur;
1161 const gchar *separator=G_DIR_SEPARATOR_S;
1162 gboolean matched = FALSE;
1166 oldpathlen = strlen(old_path);
1167 old_path_with_sep = g_strconcat(old_path,separator,NULL);
1169 for(action_cur = action_list ; action_cur != NULL ;
1170 action_cur = action_cur->next) {
1172 FilteringAction *action = action_cur->data;
1174 if (action->type == MATCHACTION_SET_TAG ||
1175 action->type == MATCHACTION_UNSET_TAG)
1177 if (!action->destination)
1180 destlen = strlen(action->destination);
1182 if (destlen > oldpathlen) {
1183 prefixlen = destlen - oldpathlen;
1184 suffix = action->destination + prefixlen;
1186 if (!strncmp(old_path, suffix, oldpathlen)) {
1187 prefix = g_malloc0(prefixlen + 1);
1188 strncpy2(prefix, action->destination, prefixlen);
1190 base = suffix + oldpathlen;
1191 while (*base == G_DIR_SEPARATOR) base++;
1193 dest_path = g_strconcat(prefix, separator,
1196 dest_path = g_strconcat(prefix,
1203 g_free(action->destination);
1204 action->destination = dest_path;
1206 } else { /* for non-leaf folders */
1207 /* compare with trailing slash */
1208 if (!strncmp(old_path_with_sep, action->destination, oldpathlen+1)) {
1210 suffix = action->destination + oldpathlen + 1;
1211 dest_path = g_strconcat(new_path, separator,
1213 g_free(action->destination);
1214 action->destination = dest_path;
1219 /* folder-moving a leaf */
1220 if (!strcmp(old_path, action->destination)) {
1221 dest_path = g_strdup(new_path);
1222 g_free(action->destination);
1223 action->destination = dest_path;
1229 g_free(old_path_with_sep);
1231 if (!strcmp(separator, G_DIR_SEPARATOR_S) && !matched) {