2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2015 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/>.
21 #include <glib/gi18n.h>
30 #include "procheader.h"
32 #include "filtering.h"
33 #include "prefs_gtk.h"
35 #include "prefs_common.h"
37 #ifndef USE_ALT_ADDRBOOK
39 #include "addressbook.h"
41 #include "addressbook-dbus.h"
42 #include "addressadd.h"
44 #include "addr_compl.h"
48 #include "addrindex.h"
49 #include "folder_item_prefs.h"
51 GSList
* pre_global_processing
= NULL
;
52 GSList
* post_global_processing
= NULL
;
53 GSList
* filtering_rules
= NULL
;
55 gboolean debug_filtering_session
= FALSE
;
57 static gboolean
filtering_is_final_action(FilteringAction
*filtering_action
);
59 FilteringAction
* filteringaction_new(int type
, int account_id
,
61 gint labelcolor
, gint score
, gchar
* header
)
63 FilteringAction
* action
;
65 action
= g_new0(FilteringAction
, 1);
68 action
->account_id
= account_id
;
70 action
->destination
= g_strdup(destination
);
72 action
->destination
= NULL
;
75 action
->header
= g_strdup(header
);
77 action
->header
= NULL
;
79 action
->labelcolor
= labelcolor
;
80 action
->score
= score
;
84 void filteringaction_free(FilteringAction
* action
)
86 cm_return_if_fail(action
);
87 g_free(action
->header
);
88 g_free(action
->destination
);
92 static gint
action_list_sort(gconstpointer a
, gconstpointer b
)
94 int first
= filtering_is_final_action((FilteringAction
*) a
) ? 1 : 0;
95 int second
= filtering_is_final_action((FilteringAction
*) b
) ? 1 : 0;
97 return (first
- second
);
100 GSList
*filtering_action_list_sort(GSList
*action_list
)
102 return g_slist_sort(action_list
, action_list_sort
);
105 FilteringProp
* filteringprop_new(gboolean enabled
,
108 MatcherList
* matchers
,
109 GSList
* action_list
)
111 FilteringProp
* filtering
;
113 filtering
= g_new0(FilteringProp
, 1);
114 filtering
->enabled
= enabled
;
115 filtering
->name
= name
? g_strdup(name
): NULL
;
116 filtering
->account_id
= account_id
;
117 filtering
->matchers
= matchers
;
118 filtering
->action_list
= filtering_action_list_sort(action_list
);
123 static FilteringAction
* filteringaction_copy(FilteringAction
* src
)
125 FilteringAction
* new;
127 new = g_new0(FilteringAction
, 1);
129 new->type
= src
->type
;
130 new->account_id
= src
->account_id
;
131 if (src
->destination
)
132 new->destination
= g_strdup(src
->destination
);
134 new->destination
= NULL
;
135 new->labelcolor
= src
->labelcolor
;
136 new->score
= src
->score
;
141 FilteringProp
* filteringprop_copy(FilteringProp
*src
)
146 new = g_new0(FilteringProp
, 1);
147 new->matchers
= g_new0(MatcherList
, 1);
149 for (tmp
= src
->matchers
->matchers
; tmp
!= NULL
&& tmp
->data
!= NULL
;) {
150 MatcherProp
*matcher
= (MatcherProp
*)tmp
->data
;
152 new->matchers
->matchers
= g_slist_append(new->matchers
->matchers
,
153 matcherprop_copy(matcher
));
157 new->matchers
->bool_and
= src
->matchers
->bool_and
;
159 new->action_list
= NULL
;
161 for (tmp
= src
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
162 FilteringAction
*filtering_action
;
164 filtering_action
= tmp
->data
;
166 new->action_list
= g_slist_append(new->action_list
,
167 filteringaction_copy(filtering_action
));
170 new->enabled
= src
->enabled
;
171 new->name
= g_strdup(src
->name
);
176 void filteringprop_free(FilteringProp
* prop
)
180 cm_return_if_fail(prop
);
181 matcherlist_free(prop
->matchers
);
183 for (tmp
= prop
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
184 filteringaction_free(tmp
->data
);
186 g_slist_free(prop
->action_list
);
191 /* move and copy messages by batches to be faster on IMAP */
192 void filtering_move_and_copy_msgs(GSList
*msgs
)
194 GSList
*messages
= g_slist_copy(msgs
);
195 FolderItem
*last_item
= NULL
;
196 FiltOp cur_op
= IS_NOTHING
;
198 debug_print("checking %d messages\n", g_slist_length(msgs
));
200 GSList
*batch
= NULL
, *cur
;
202 for (cur
= messages
; cur
; cur
= cur
->next
) {
203 MsgInfo
*info
= (MsgInfo
*)cur
->data
;
204 if (last_item
== NULL
) {
205 if (info
->filter_op
== IS_COPY
|| info
->filter_op
== IS_MOVE
)
206 last_item
= info
->to_filter_folder
;
207 else if (info
->filter_op
== IS_DELE
)
208 last_item
= info
->folder
;
210 if (last_item
== NULL
)
212 if (cur_op
== IS_NOTHING
) {
213 if (info
->filter_op
== IS_COPY
)
215 else if (info
->filter_op
== IS_MOVE
)
217 else if (info
->filter_op
== IS_DELE
)
220 if (info
->filter_op
== IS_COPY
|| info
->filter_op
== IS_MOVE
) {
221 if (info
->to_filter_folder
== last_item
222 && cur_op
== info
->filter_op
) {
224 batch
= g_slist_prepend(batch
, info
);
226 } else if (info
->filter_op
== IS_DELE
) {
227 if (info
->folder
== last_item
228 && cur_op
== info
->filter_op
) {
230 batch
= g_slist_prepend(batch
, info
);
235 debug_print("no more messages to move/copy/del\n");
238 debug_print("%d messages to %s in %s\n", found
,
239 cur_op
==IS_COPY
? "copy":(cur_op
==IS_DELE
?"delete":"move"),
240 last_item
->name
? last_item
->name
:"(noname)");
242 for (cur
= batch
; cur
; cur
= cur
->next
) {
243 MsgInfo
*info
= (MsgInfo
*)cur
->data
;
244 messages
= g_slist_remove(messages
, info
);
245 info
->to_filter_folder
= NULL
;
246 info
->filter_op
= IS_NOTHING
;
248 batch
= g_slist_reverse(batch
);
249 if (g_slist_length(batch
)) {
250 MsgInfo
*info
= (MsgInfo
*)batch
->data
;
251 if (cur_op
== IS_COPY
&& last_item
!= info
->folder
) {
252 folder_item_copy_msgs(last_item
, batch
);
253 } else if (cur_op
== IS_MOVE
&& last_item
!= info
->folder
) {
254 if (folder_item_move_msgs(last_item
, batch
) < 0)
255 folder_item_move_msgs(
256 folder_get_default_inbox(),
258 } else if (cur_op
== IS_DELE
&& last_item
== info
->folder
) {
259 folder_item_remove_msgs(last_item
, batch
);
261 /* we don't reference the msginfos, because caller will do */
262 if (prefs_common
.real_time_sync
)
263 folder_item_synchronise(last_item
);
271 /* we don't reference the msginfos, because caller will do */
272 g_slist_free(messages
);
276 fitleringaction_apply
277 runs the action on one MsgInfo
278 return value : return TRUE if the action could be applied
281 #define FLUSH_COPY_IF_NEEDED(info) { \
282 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
283 debug_print("must debatch pending copy\n"); \
284 folder_item_copy_msg(info->to_filter_folder, info); \
285 info->filter_op = IS_NOTHING; \
289 static gboolean
filteringaction_apply(FilteringAction
* action
, MsgInfo
* info
)
291 FolderItem
* dest_folder
;
294 PrefsAccount
* account
;
297 switch(action
->type
) {
298 case MATCHACTION_MOVE
:
299 if (MSG_IS_LOCKED(info
->flags
))
303 folder_find_item_from_identifier(action
->destination
);
305 debug_print("*** folder not found '%s'\n",
306 action
->destination
?action
->destination
:"(null)");
310 FLUSH_COPY_IF_NEEDED(info
);
311 /* mark message to be moved */
312 info
->filter_op
= IS_MOVE
;
313 info
->to_filter_folder
= dest_folder
;
316 case MATCHACTION_COPY
:
318 folder_find_item_from_identifier(action
->destination
);
321 debug_print("*** folder not found '%s'\n",
322 action
->destination
?action
->destination
:"(null)");
326 FLUSH_COPY_IF_NEEDED(info
);
327 /* mark message to be copied */
328 info
->filter_op
= IS_COPY
;
329 info
->to_filter_folder
= dest_folder
;
332 case MATCHACTION_SET_TAG
:
333 case MATCHACTION_UNSET_TAG
:
334 val
= tags_get_id_for_str(action
->destination
);
336 debug_print("*** tag '%s' not found\n",
337 action
->destination
?action
->destination
:"(null)");
340 FLUSH_COPY_IF_NEEDED(info
);
341 procmsg_msginfo_update_tags(info
, (action
->type
== MATCHACTION_SET_TAG
), val
);
344 case MATCHACTION_CLEAR_TAGS
:
345 FLUSH_COPY_IF_NEEDED(info
);
346 procmsg_msginfo_clear_tags(info
);
349 case MATCHACTION_DELETE
:
350 FLUSH_COPY_IF_NEEDED(info
);
351 info
->filter_op
= IS_DELE
;
354 case MATCHACTION_MARK
:
355 FLUSH_COPY_IF_NEEDED(info
);
356 procmsg_msginfo_set_flags(info
, MSG_MARKED
, 0);
359 case MATCHACTION_UNMARK
:
360 FLUSH_COPY_IF_NEEDED(info
);
361 procmsg_msginfo_unset_flags(info
, MSG_MARKED
, 0);
364 case MATCHACTION_LOCK
:
365 FLUSH_COPY_IF_NEEDED(info
);
366 procmsg_msginfo_set_flags(info
, MSG_LOCKED
, 0);
369 case MATCHACTION_UNLOCK
:
370 FLUSH_COPY_IF_NEEDED(info
);
371 procmsg_msginfo_unset_flags(info
, MSG_LOCKED
, 0);
374 case MATCHACTION_MARK_AS_READ
:
375 FLUSH_COPY_IF_NEEDED(info
);
376 procmsg_msginfo_unset_flags(info
, MSG_UNREAD
| MSG_NEW
, 0);
379 case MATCHACTION_MARK_AS_UNREAD
:
380 FLUSH_COPY_IF_NEEDED(info
);
381 procmsg_msginfo_change_flags(info
, MSG_UNREAD
, 0, MSG_NEW
, 0);
384 case MATCHACTION_MARK_AS_SPAM
:
385 FLUSH_COPY_IF_NEEDED(info
);
386 procmsg_spam_learner_learn(info
, NULL
, TRUE
);
387 procmsg_msginfo_change_flags(info
, MSG_SPAM
, 0, MSG_NEW
|MSG_UNREAD
, 0);
390 case MATCHACTION_MARK_AS_HAM
:
391 FLUSH_COPY_IF_NEEDED(info
);
392 procmsg_spam_learner_learn(info
, NULL
, FALSE
);
393 procmsg_msginfo_unset_flags(info
, MSG_SPAM
, 0);
396 case MATCHACTION_COLOR
:
397 FLUSH_COPY_IF_NEEDED(info
);
398 procmsg_msginfo_unset_flags(info
, MSG_CLABEL_FLAG_MASK
, 0);
399 procmsg_msginfo_set_flags(info
, MSG_COLORLABEL_TO_FLAGS(action
->labelcolor
), 0);
402 case MATCHACTION_FORWARD
:
403 case MATCHACTION_FORWARD_AS_ATTACHMENT
:
404 account
= account_find_from_id(action
->account_id
);
405 compose
= compose_forward(account
, info
,
406 action
->type
== MATCHACTION_FORWARD
? FALSE
: TRUE
,
408 compose_entry_append(compose
, action
->destination
,
409 compose
->account
->protocol
== A_NNTP
411 : COMPOSE_TO
, PREF_NONE
);
413 val
= compose_send(compose
);
415 return val
== 0 ? TRUE
: FALSE
;
417 case MATCHACTION_REDIRECT
:
418 account
= account_find_from_id(action
->account_id
);
419 compose
= compose_redirect(account
, info
, TRUE
);
420 if (compose
->account
->protocol
== A_NNTP
)
423 compose_entry_append(compose
, action
->destination
,
424 COMPOSE_TO
, PREF_NONE
);
426 val
= compose_send(compose
);
428 return val
== 0 ? TRUE
: FALSE
;
430 case MATCHACTION_EXECUTE
:
431 cmd
= matching_build_command(action
->destination
, info
);
435 if (system(cmd
) == -1)
436 g_warning("couldn't run %s", cmd
);
441 case MATCHACTION_SET_SCORE
:
442 FLUSH_COPY_IF_NEEDED(info
);
443 info
->score
= action
->score
;
446 case MATCHACTION_CHANGE_SCORE
:
447 FLUSH_COPY_IF_NEEDED(info
);
448 info
->score
+= action
->score
;
451 case MATCHACTION_STOP
:
454 case MATCHACTION_HIDE
:
455 FLUSH_COPY_IF_NEEDED(info
);
459 case MATCHACTION_IGNORE
:
460 FLUSH_COPY_IF_NEEDED(info
);
461 procmsg_msginfo_set_flags(info
, MSG_IGNORE_THREAD
, 0);
464 case MATCHACTION_WATCH
:
465 FLUSH_COPY_IF_NEEDED(info
);
466 procmsg_msginfo_set_flags(info
, MSG_WATCH_THREAD
, 0);
469 case MATCHACTION_ADD_TO_ADDRESSBOOK
:
471 #ifndef USE_ALT_ADDRBOOK
472 AddressDataSource
*book
= NULL
;
473 AddressBookFile
*abf
= NULL
;
474 ItemFolder
*folder
= NULL
;
480 #ifndef USE_ALT_ADDRBOOK
481 if (!addressbook_peek_folder_exists(action
->destination
, &book
, &folder
)) {
482 g_warning("addressbook folder not found '%s'", action
->destination
?action
->destination
:"(null)");
486 g_warning("addressbook_peek_folder_exists returned NULL book");
490 abf
= book
->rawDataSource
;
493 if (procheader_get_header_from_msginfo(info
, &buf
, action
->header
) < 0)
496 header
= procheader_parse_header(buf
);
499 /* add all addresses that are not already in */
500 if (header
&& *header
->body
&& (*header
->body
!= '\0')) {
501 GSList
*address_list
= NULL
;
505 if (action
->destination
== NULL
||
506 strcasecmp(action
->destination
, "Any") == 0 ||
507 *(action
->destination
) == '\0')
510 path
= action
->destination
;
511 start_address_completion(path
);
513 address_list
= address_list_append(address_list
, header
->body
);
514 for (walk
= address_list
; walk
!= NULL
; walk
= walk
->next
) {
515 gchar
*stripped_addr
= g_strdup(walk
->data
);
516 extract_address(stripped_addr
);
518 if (complete_matches_found(walk
->data
) == 0) {
519 gchar
*name
= procheader_get_fromname(walk
->data
);
520 debug_print("adding address '%s' to addressbook '%s'\n",
521 stripped_addr
, action
->destination
);
522 #ifndef USE_ALT_ADDRBOOK
523 if (!addrbook_add_contact(abf
, folder
, name
, stripped_addr
, NULL
)) {
525 if (!addressadd_selection(name
, stripped_addr
, NULL
, NULL
)) {
527 g_warning("contact could not be added");
532 debug_print("address '%s' already found in addressbook '%s', skipping\n",
533 stripped_addr
, action
->destination
);
535 g_free(stripped_addr
);
538 g_slist_free(address_list
);
539 end_address_completion();
541 g_warning("header '%s' not set or empty", action
->header
?action
->header
:"(null)");
543 return (errors
== 0);
551 gboolean
filteringaction_apply_action_list(GSList
*action_list
, MsgInfo
*info
)
554 cm_return_val_if_fail(action_list
, FALSE
);
555 cm_return_val_if_fail(info
, FALSE
);
556 for (p
= action_list
; p
&& p
->data
; p
= g_slist_next(p
)) {
557 FilteringAction
*a
= (FilteringAction
*) p
->data
;
558 if (filteringaction_apply(a
, info
)) {
559 if (filtering_is_final_action(a
))
568 static gboolean
filtering_match_condition(FilteringProp
*filtering
, MsgInfo
*info
,
569 PrefsAccount
*ac_prefs
)
571 /* this function returns true if a filtering rule applies regarding to its account
572 data and if it does, if the conditions list match.
574 per-account data of a filtering rule is either matched against current account
575 when filtering is done manually, or against the account currently used for
576 retrieving messages when it's an manual or automatic fetching of messages.
577 per-account data match doesn't apply to pre-/post-/folder-processing rules.
579 when filtering messages manually:
580 - either the filtering rule is not account-based and it will be processed
581 - or it's per-account and we check if we HAVE TO match it against the current
582 account (according to user-land settings, per-account rules might have to
583 be skipped, or only the rules that match the current account have to be
584 applied, or all rules will have to be applied regardless to if they are
585 account-based or not)
587 notes about debugging output in that function:
588 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
589 no debug output is done when filtering_debug_level is low
592 gboolean matches
= FALSE
;
594 if (ac_prefs
!= NULL
) {
595 matches
= ((filtering
->account_id
== 0)
596 || (filtering
->account_id
== ac_prefs
->account_id
));
599 if (debug_filtering_session
) {
600 if (matches
&& prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
601 if (filtering
->account_id
== 0) {
602 log_status_ok(LOG_DEBUG_FILTERING
,
603 _("rule is not account-based\n"));
605 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
606 log_status_ok(LOG_DEBUG_FILTERING
,
607 _("rule is account-based [id=%d, name='%s'], "
608 "matching the account currently used to retrieve messages\n"),
609 ac_prefs
->account_id
, ac_prefs
?ac_prefs
->account_name
:_("NON_EXISTENT"));
615 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
616 log_status_skip(LOG_DEBUG_FILTERING
,
617 _("rule is account-based, "
618 "not matching the account currently used to retrieve messages\n"));
620 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
621 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
623 log_status_skip(LOG_DEBUG_FILTERING
,
624 _("rule is account-based [id=%d, name='%s'], "
625 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
626 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
627 ac_prefs
->account_id
, ac_prefs
?ac_prefs
->account_name
:_("NON_EXISTENT"));
633 switch (prefs_common
.apply_per_account_filtering_rules
) {
634 case FILTERING_ACCOUNT_RULES_FORCE
:
635 /* apply filtering rules regardless to the account info */
639 if (debug_filtering_session
) {
640 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
641 if (filtering
->account_id
== 0) {
642 log_status_ok(LOG_DEBUG_FILTERING
,
643 _("rule is not account-based, "
644 "all rules are applied on user request anyway\n"));
646 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
648 log_status_ok(LOG_DEBUG_FILTERING
,
649 _("rule is account-based [id=%d, name='%s'], "
650 "but all rules are applied on user request\n"),
651 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"));
656 case FILTERING_ACCOUNT_RULES_SKIP
:
657 /* don't apply filtering rules that belong to an account */
658 matches
= (filtering
->account_id
== 0);
661 if (debug_filtering_session
) {
663 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
664 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
666 log_status_skip(LOG_DEBUG_FILTERING
,
667 _("rule is account-based [id=%d, name='%s'], "
668 "skipped on user request\n"),
669 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"));
671 log_status_skip(LOG_DEBUG_FILTERING
,
672 _("rule is account-based, "
673 "skipped on user request\n"));
676 if (matches
&& prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
677 log_status_ok(LOG_DEBUG_FILTERING
,
678 _("rule is not account-based\n"));
683 case FILTERING_ACCOUNT_RULES_USE_CURRENT
:
684 matches
= ((filtering
->account_id
== 0)
685 || (filtering
->account_id
== cur_account
->account_id
));
688 if (debug_filtering_session
) {
690 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
691 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
693 log_status_skip(LOG_DEBUG_FILTERING
,
694 _("rule is account-based [id=%d, name='%s'], "
695 "not matching current account [id=%d, name='%s']\n"),
696 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
697 cur_account
->account_id
, cur_account
?cur_account
->account_name
:_("NON_EXISTENT"));
699 log_status_skip(LOG_DEBUG_FILTERING
,
700 _("rule is account-based, "
701 "not matching current account\n"));
704 if (matches
&& prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
705 if (filtering
->account_id
== 0) {
706 log_status_ok(LOG_DEBUG_FILTERING
,
707 _("rule is not account-based\n"));
709 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
711 log_status_ok(LOG_DEBUG_FILTERING
,
712 _("rule is account-based [id=%d, name='%s'], "
713 "current account [id=%d, name='%s']\n"),
714 account
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
715 cur_account
->account_id
, cur_account
?cur_account
->account_name
:_("NON_EXISTENT"));
724 return matches
&& matcherlist_match(filtering
->matchers
, info
);
728 *\brief Apply a rule on message.
730 *\param filtering List of filtering rules.
731 *\param info Message to apply rules on.
732 *\param final Variable returning TRUE or FALSE if one of the
733 * encountered actions was final.
734 * See also \ref filtering_is_final_action.
736 *\return gboolean TRUE to continue applying rules.
738 static gboolean
filtering_apply_rule(FilteringProp
*filtering
, MsgInfo
*info
,
741 gboolean result
= TRUE
;
746 for (tmp
= filtering
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
747 FilteringAction
* action
;
750 buf
= filteringaction_to_string(action
);
751 if (debug_filtering_session
)
752 log_print(LOG_DEBUG_FILTERING
, _("applying action [ %s ]\n"), buf
);
754 if (FALSE
== (result
= filteringaction_apply(action
, info
))) {
755 if (debug_filtering_session
) {
756 if (action
->type
!= MATCHACTION_STOP
)
757 log_warning(LOG_DEBUG_FILTERING
, _("action could not apply\n"));
758 log_print(LOG_DEBUG_FILTERING
,
759 _("no further processing after action [ %s ]\n"), buf
);
761 debug_print("No further processing after rule %s\n", buf
);
764 if (filtering_is_final_action(action
)) {
774 *\brief Check if an action is "final", i.e. should break further
777 *\param filtering_action Action to check.
779 *\return gboolean TRUE if \a filtering_action is final.
781 static gboolean
filtering_is_final_action(FilteringAction
*filtering_action
)
783 switch(filtering_action
->type
) {
784 case MATCHACTION_MOVE
:
785 case MATCHACTION_DELETE
:
786 case MATCHACTION_STOP
:
787 case MATCHACTION_MARK_AS_SPAM
:
788 return TRUE
; /* MsgInfo invalid for message */
794 static gboolean
filter_msginfo(GSList
* filtering_list
, MsgInfo
* info
, PrefsAccount
* ac_prefs
)
800 cm_return_val_if_fail(info
!= NULL
, TRUE
);
802 for (l
= filtering_list
, final
= FALSE
, apply_next
= FALSE
; l
!= NULL
; l
= g_slist_next(l
)) {
803 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
805 if (filtering
->enabled
) {
806 if (debug_filtering_session
) {
807 gchar
*buf
= filteringprop_to_string(filtering
);
808 if (filtering
->name
&& *filtering
->name
!= '\0') {
809 log_print(LOG_DEBUG_FILTERING
,
810 _("processing rule '%s' [ %s ]\n"),
811 filtering
->name
, buf
);
813 log_print(LOG_DEBUG_FILTERING
,
814 _("processing rule <unnamed> [ %s ]\n"),
820 if (filtering_match_condition(filtering
, info
, ac_prefs
)) {
821 apply_next
= filtering_apply_rule(filtering
, info
, &final
);
827 if (debug_filtering_session
) {
828 gchar
*buf
= filteringprop_to_string(filtering
);
829 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
830 if (filtering
->name
&& *filtering
->name
!= '\0') {
831 log_status_skip(LOG_DEBUG_FILTERING
,
832 _("disabled rule '%s' [ %s ]\n"),
833 filtering
->name
, buf
);
835 log_status_skip(LOG_DEBUG_FILTERING
,
836 _("disabled rule <unnamed> [ %s ]\n"),
845 /* put in inbox if the last rule was not a final one, or
846 * a final rule could not be applied.
847 * Either of these cases is likely. */
848 if (!final
|| !apply_next
) {
856 *\brief Filter a message against a list of rules.
858 *\param flist List of filter rules.
859 *\param info Message.
861 *\return gboolean TRUE if filter rules handled the message.
863 *\note Returning FALSE means the message was not handled,
864 * and that the calling code should do the default
865 * processing. E.g. \ref inc.c::inc_start moves the
866 * message to the inbox.
868 gboolean
filter_message_by_msginfo(GSList
*flist
, MsgInfo
*info
, PrefsAccount
* ac_prefs
,
869 FilteringInvocationType context
, gchar
*extra_info
)
873 if (prefs_common
.enable_filtering_debug
) {
874 gchar
*tmp
= _("undetermined");
877 case FILTERING_INCORPORATION
:
878 tmp
= _("incorporation");
879 debug_filtering_session
= prefs_common
.enable_filtering_debug_inc
;
881 case FILTERING_MANUALLY
:
883 debug_filtering_session
= prefs_common
.enable_filtering_debug_manual
;
885 case FILTERING_FOLDER_PROCESSING
:
886 tmp
= _("folder processing");
887 debug_filtering_session
= prefs_common
.enable_filtering_debug_folder_proc
;
889 case FILTERING_PRE_PROCESSING
:
890 tmp
= _("pre-processing");
891 debug_filtering_session
= prefs_common
.enable_filtering_debug_pre_proc
;
893 case FILTERING_POST_PROCESSING
:
894 tmp
= _("post-processing");
895 debug_filtering_session
= prefs_common
.enable_filtering_debug_post_proc
;
898 debug_filtering_session
= FALSE
;
902 debug_filtering_session
= FALSE
;
904 if (debug_filtering_session
) {
905 gchar
*file
= procmsg_get_message_file_path(info
);
906 gchar
*spc
= g_strnfill(LOG_TIME_LEN
+ 1, ' ');
908 /* show context info and essential info about the message */
909 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
910 log_print(LOG_DEBUG_FILTERING
,
911 _("filtering message (%s%s%s)\n"
912 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
913 tmp
, extra_info
? _(": ") : "", extra_info
? extra_info
: "",
914 spc
, file
, spc
, prefs_common_translated_header_name("Date:"), info
->date
,
915 spc
, prefs_common_translated_header_name("From:"), info
->from
,
916 spc
, prefs_common_translated_header_name("To:"), info
->to
,
917 spc
, prefs_common_translated_header_name("Subject:"), info
->subject
);
919 log_print(LOG_DEBUG_FILTERING
,
920 _("filtering message (%s%s%s)\n"
921 "%smessage file: %s\n"),
922 tmp
, extra_info
? _(": ") : "", extra_info
? extra_info
: "",
929 debug_filtering_session
= FALSE
;
931 ret
= filter_msginfo(flist
, info
, ac_prefs
);
932 debug_filtering_session
= FALSE
;
936 gchar
*filteringaction_to_string(FilteringAction
*action
)
938 const gchar
*command_str
;
940 gchar
* quoted_header
;
941 GString
*dest
= g_string_new("");
942 gchar
*deststr
= NULL
;
944 command_str
= get_matchparser_tab_str(action
->type
);
946 if (command_str
== NULL
)
949 switch(action
->type
) {
950 case MATCHACTION_MOVE
:
951 case MATCHACTION_COPY
:
952 case MATCHACTION_EXECUTE
:
953 case MATCHACTION_SET_TAG
:
954 case MATCHACTION_UNSET_TAG
:
955 quoted_dest
= matcher_quote_str(action
->destination
);
956 g_string_append_printf(dest
, "%s \"%s\"", command_str
, quoted_dest
);
960 case MATCHACTION_DELETE
:
961 case MATCHACTION_MARK
:
962 case MATCHACTION_UNMARK
:
963 case MATCHACTION_LOCK
:
964 case MATCHACTION_UNLOCK
:
965 case MATCHACTION_MARK_AS_READ
:
966 case MATCHACTION_MARK_AS_UNREAD
:
967 case MATCHACTION_MARK_AS_SPAM
:
968 case MATCHACTION_MARK_AS_HAM
:
969 case MATCHACTION_STOP
:
970 case MATCHACTION_HIDE
:
971 case MATCHACTION_IGNORE
:
972 case MATCHACTION_WATCH
:
973 case MATCHACTION_CLEAR_TAGS
:
974 g_string_append_printf(dest
, "%s", command_str
);
977 case MATCHACTION_REDIRECT
:
978 case MATCHACTION_FORWARD
:
979 case MATCHACTION_FORWARD_AS_ATTACHMENT
:
980 quoted_dest
= matcher_quote_str(action
->destination
);
981 g_string_append_printf(dest
, "%s %d \"%s\"", command_str
, action
->account_id
, quoted_dest
);
985 case MATCHACTION_COLOR
:
986 g_string_append_printf(dest
, "%s %d", command_str
, action
->labelcolor
);
989 case MATCHACTION_CHANGE_SCORE
:
990 case MATCHACTION_SET_SCORE
:
991 g_string_append_printf(dest
, "%s %d", command_str
, action
->score
);
994 case MATCHACTION_ADD_TO_ADDRESSBOOK
:
995 quoted_header
= matcher_quote_str(action
->header
);
996 quoted_dest
= matcher_quote_str(action
->destination
);
997 g_string_append_printf(dest
, "%s \"%s\" \"%s\"", command_str
, quoted_header
, quoted_dest
);
999 g_free(quoted_header
);
1005 deststr
= dest
->str
;
1006 g_string_free(dest
, FALSE
);
1010 gchar
* filteringaction_list_to_string(GSList
* action_list
)
1012 gchar
*action_list_str
;
1016 action_list_str
= NULL
;
1017 for (tmp
= action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
1019 FilteringAction
* action
;
1023 action_str
= filteringaction_to_string(action
);
1025 if (action_list_str
!= NULL
) {
1026 list_str
= g_strconcat(action_list_str
, " ", action_str
, NULL
);
1027 g_free(action_list_str
);
1030 list_str
= g_strdup(action_str
);
1033 action_list_str
= list_str
;
1036 return action_list_str
;
1039 gchar
* filteringprop_to_string(FilteringProp
* prop
)
1042 gchar
*action_list_str
;
1043 gchar
*filtering_str
;
1048 action_list_str
= filteringaction_list_to_string(prop
->action_list
);
1050 if (action_list_str
== NULL
)
1053 list_str
= matcherlist_to_string(prop
->matchers
);
1055 if (list_str
== NULL
) {
1056 g_free(action_list_str
);
1060 filtering_str
= g_strconcat(list_str
, " ", action_list_str
, NULL
);
1061 g_free(action_list_str
);
1064 return filtering_str
;
1067 static void prefs_filtering_free(GSList
* prefs_filtering
)
1069 while (prefs_filtering
!= NULL
) {
1070 FilteringProp
* filtering
= (FilteringProp
*)
1071 prefs_filtering
->data
;
1072 filteringprop_free(filtering
);
1073 prefs_filtering
= g_slist_remove(prefs_filtering
, filtering
);
1077 static gboolean
prefs_filtering_free_func(GNode
*node
, gpointer data
)
1079 FolderItem
*item
= node
->data
;
1081 cm_return_val_if_fail(item
, FALSE
);
1082 cm_return_val_if_fail(item
->prefs
, FALSE
);
1084 prefs_filtering_free(item
->prefs
->processing
);
1085 item
->prefs
->processing
= NULL
;
1090 void prefs_filtering_clear(void)
1094 for (cur
= folder_get_list() ; cur
!= NULL
; cur
= g_list_next(cur
)) {
1097 folder
= (Folder
*) cur
->data
;
1098 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1099 prefs_filtering_free_func
, NULL
);
1102 prefs_filtering_free(filtering_rules
);
1103 filtering_rules
= NULL
;
1104 prefs_filtering_free(pre_global_processing
);
1105 pre_global_processing
= NULL
;
1106 prefs_filtering_free(post_global_processing
);
1107 post_global_processing
= NULL
;
1110 void prefs_filtering_clear_folder(Folder
*folder
)
1112 cm_return_if_fail(folder
);
1113 cm_return_if_fail(folder
->node
);
1115 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1116 prefs_filtering_free_func
, NULL
);
1117 /* FIXME: Note folder settings were changed, where the updates? */
1120 gboolean
filtering_peek_per_account_rules(GSList
*filtering_list
)
1121 /* return TRUE if there's at least one per-account filtering rule */
1125 for (l
= filtering_list
; l
!= NULL
; l
= g_slist_next(l
)) {
1126 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
1128 if (filtering
->enabled
&& (filtering
->account_id
!= 0)) {
1136 gboolean
filtering_action_list_rename_path(GSList
*action_list
, const gchar
*old_path
,
1137 const gchar
*new_path
)
1143 gchar
*old_path_with_sep
;
1147 GSList
* action_cur
;
1148 const gchar
*separator
=G_DIR_SEPARATOR_S
;
1149 gboolean matched
= FALSE
;
1153 oldpathlen
= strlen(old_path
);
1154 old_path_with_sep
= g_strconcat(old_path
,separator
,NULL
);
1156 for(action_cur
= action_list
; action_cur
!= NULL
;
1157 action_cur
= action_cur
->next
) {
1159 FilteringAction
*action
= action_cur
->data
;
1161 if (action
->type
== MATCHACTION_SET_TAG
||
1162 action
->type
== MATCHACTION_UNSET_TAG
)
1164 if (!action
->destination
)
1167 destlen
= strlen(action
->destination
);
1169 if (destlen
> oldpathlen
) {
1170 prefixlen
= destlen
- oldpathlen
;
1171 suffix
= action
->destination
+ prefixlen
;
1173 if (!strncmp(old_path
, suffix
, oldpathlen
)) {
1174 prefix
= g_malloc0(prefixlen
+ 1);
1175 strncpy2(prefix
, action
->destination
, prefixlen
);
1177 base
= suffix
+ oldpathlen
;
1178 while (*base
== G_DIR_SEPARATOR
) base
++;
1180 dest_path
= g_strconcat(prefix
, separator
,
1183 dest_path
= g_strconcat(prefix
,
1190 g_free(action
->destination
);
1191 action
->destination
= dest_path
;
1193 } else { /* for non-leaf folders */
1194 /* compare with trailing slash */
1195 if (!strncmp(old_path_with_sep
, action
->destination
, oldpathlen
+1)) {
1197 suffix
= action
->destination
+ oldpathlen
+ 1;
1198 dest_path
= g_strconcat(new_path
, separator
,
1200 g_free(action
->destination
);
1201 action
->destination
= dest_path
;
1206 /* folder-moving a leaf */
1207 if (!strcmp(old_path
, action
->destination
)) {
1208 dest_path
= g_strdup(new_path
);
1209 g_free(action
->destination
);
1210 action
->destination
= dest_path
;
1216 g_free(old_path_with_sep
);
1218 if (!strcmp(separator
, G_DIR_SEPARATOR_S
) && !matched
) {