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 if (procheader_get_header_from_msginfo(info
, buf
,
512 sizeof(buf
), action
->header
) < 0)
515 header
= procheader_parse_header(buf
);
517 /* add all addresses that are not already in */
518 if (header
&& *header
->body
&& (*header
->body
!= '\0')) {
519 GSList
*address_list
= NULL
;
523 if (action
->destination
== NULL
||
524 strcasecmp(action
->destination
, "Any") == 0 ||
525 *(action
->destination
) == '\0')
528 path
= action
->destination
;
529 start_address_completion(path
);
531 address_list
= address_list_append(address_list
, header
->body
);
532 for (walk
= address_list
; walk
!= NULL
; walk
= walk
->next
) {
533 gchar
*stripped_addr
= g_strdup(walk
->data
);
534 extract_address(stripped_addr
);
536 if (complete_matches_found(walk
->data
) == 0) {
537 debug_print("adding address '%s' to addressbook '%s'\n",
538 stripped_addr
, action
->destination
);
539 #ifndef USE_NEW_ADDRBOOK
540 if (!addrbook_add_contact(abf
, folder
, stripped_addr
, stripped_addr
, NULL
)) {
542 if (!addressadd_selection(NULL
, stripped_addr
, NULL
, NULL
)) {
544 g_warning("contact could not been added\n");
548 debug_print("address '%s' already found in addressbook '%s', skipping\n",
549 stripped_addr
, action
->destination
);
551 g_free(stripped_addr
);
554 g_slist_free(address_list
);
555 end_address_completion();
557 g_warning("header '%s' not set or empty\n", action
->header
?action
->header
:"(null)");
559 return (errors
== 0);
567 gboolean
filteringaction_apply_action_list(GSList
*action_list
, MsgInfo
*info
)
570 cm_return_val_if_fail(action_list
, FALSE
);
571 cm_return_val_if_fail(info
, FALSE
);
572 for (p
= action_list
; p
&& p
->data
; p
= g_slist_next(p
)) {
573 FilteringAction
*a
= (FilteringAction
*) p
->data
;
574 if (filteringaction_apply(a
, info
)) {
575 if (filtering_is_final_action(a
))
584 static gboolean
filtering_match_condition(FilteringProp
*filtering
, MsgInfo
*info
,
585 PrefsAccount
*ac_prefs
)
587 /* this function returns true if a filtering rule applies regarding to its account
588 data and if it does, if the conditions list match.
590 per-account data of a filtering rule is either matched against current account
591 when filtering is done manually, or against the account currently used for
592 retrieving messages when it's an manual or automatic fetching of messages.
593 per-account data match doesn't apply to pre-/post-/folder-processing rules.
595 when filtering messages manually:
596 - either the filtering rule is not account-based and it will be processed
597 - or it's per-account and we check if we HAVE TO match it against the current
598 account (according to user-land settings, per-account rules might have to
599 be skipped, or only the rules that match the current account have to be
600 applied, or all rules will have to be applied regardless to if they are
601 account-based or not)
603 notes about debugging output in that function:
604 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
605 no debug output is done when filtering_debug_level is low
608 gboolean matches
= FALSE
;
610 if (ac_prefs
!= NULL
) {
611 matches
= ((filtering
->account_id
== 0)
612 || (filtering
->account_id
== ac_prefs
->account_id
));
615 if (debug_filtering_session
) {
616 if (matches
&& prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
617 if (filtering
->account_id
== 0) {
618 log_status_ok(LOG_DEBUG_FILTERING
,
619 _("rule is not account-based\n"));
621 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
622 log_status_ok(LOG_DEBUG_FILTERING
,
623 _("rule is account-based [id=%d, name='%s'], "
624 "matching the account currently used to retrieve messages\n"),
625 ac_prefs
->account_id
, ac_prefs
?ac_prefs
->account_name
:_("NON_EXISTENT"));
631 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
632 log_status_skip(LOG_DEBUG_FILTERING
,
633 _("rule is account-based, "
634 "not matching the account currently used to retrieve messages\n"));
636 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
637 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
639 log_status_skip(LOG_DEBUG_FILTERING
,
640 _("rule is account-based [id=%d, name='%s'], "
641 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
642 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
643 ac_prefs
->account_id
, ac_prefs
?ac_prefs
->account_name
:_("NON_EXISTENT"));
649 switch (prefs_common
.apply_per_account_filtering_rules
) {
650 case FILTERING_ACCOUNT_RULES_FORCE
:
651 /* apply filtering rules regardless to the account info */
655 if (debug_filtering_session
) {
656 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
657 if (filtering
->account_id
== 0) {
658 log_status_ok(LOG_DEBUG_FILTERING
,
659 _("rule is not account-based, "
660 "all rules are applied on user request anyway\n"));
662 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
664 log_status_ok(LOG_DEBUG_FILTERING
,
665 _("rule is account-based [id=%d, name='%s'], "
666 "but all rules are applied on user request\n"),
667 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"));
672 case FILTERING_ACCOUNT_RULES_SKIP
:
673 /* don't apply filtering rules that belong to an account */
674 matches
= (filtering
->account_id
== 0);
677 if (debug_filtering_session
) {
679 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
680 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
682 log_status_skip(LOG_DEBUG_FILTERING
,
683 _("rule is account-based [id=%d, name='%s'], "
684 "skipped on user request\n"),
685 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"));
687 log_status_skip(LOG_DEBUG_FILTERING
,
688 _("rule is account-based, "
689 "skipped on user request\n"));
692 if (matches
&& prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
693 log_status_ok(LOG_DEBUG_FILTERING
,
694 _("rule is not account-based\n"));
699 case FILTERING_ACCOUNT_RULES_USE_CURRENT
:
700 matches
= ((filtering
->account_id
== 0)
701 || (filtering
->account_id
== cur_account
->account_id
));
704 if (debug_filtering_session
) {
706 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
707 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
709 log_status_skip(LOG_DEBUG_FILTERING
,
710 _("rule is account-based [id=%d, name='%s'], "
711 "not matching current account [id=%d, name='%s']\n"),
712 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
713 cur_account
->account_id
, cur_account
?cur_account
->account_name
:_("NON_EXISTENT"));
715 log_status_skip(LOG_DEBUG_FILTERING
,
716 _("rule is account-based, "
717 "not matching current account\n"));
720 if (matches
&& prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
721 if (filtering
->account_id
== 0) {
722 log_status_ok(LOG_DEBUG_FILTERING
,
723 _("rule is not account-based\n"));
725 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
727 log_status_ok(LOG_DEBUG_FILTERING
,
728 _("rule is account-based [id=%d, name='%s'], "
729 "current account [id=%d, name='%s']\n"),
730 account
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
731 cur_account
->account_id
, cur_account
?cur_account
->account_name
:_("NON_EXISTENT"));
740 return matches
&& matcherlist_match(filtering
->matchers
, info
);
744 *\brief Apply a rule on message.
746 *\param filtering List of filtering rules.
747 *\param info Message to apply rules on.
748 *\param final Variable returning TRUE or FALSE if one of the
749 * encountered actions was final.
750 * See also \ref filtering_is_final_action.
752 *\return gboolean TRUE to continue applying rules.
754 static gboolean
filtering_apply_rule(FilteringProp
*filtering
, MsgInfo
*info
,
757 gboolean result
= TRUE
;
762 for (tmp
= filtering
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
763 FilteringAction
* action
;
766 buf
= filteringaction_to_string(action
);
767 if (debug_filtering_session
)
768 log_print(LOG_DEBUG_FILTERING
, _("applying action [ %s ]\n"), buf
);
770 if (FALSE
== (result
= filteringaction_apply(action
, info
))) {
771 if (debug_filtering_session
) {
772 if (action
->type
!= MATCHACTION_STOP
)
773 log_warning(LOG_DEBUG_FILTERING
, _("action could not apply\n"));
774 log_print(LOG_DEBUG_FILTERING
,
775 _("no further processing after action [ %s ]\n"), buf
);
777 debug_print("No further processing after rule %s\n", buf
);
780 if (filtering_is_final_action(action
)) {
790 *\brief Check if an action is "final", i.e. should break further
793 *\param filtering_action Action to check.
795 *\return gboolean TRUE if \a filtering_action is final.
797 static gboolean
filtering_is_final_action(FilteringAction
*filtering_action
)
799 switch(filtering_action
->type
) {
800 case MATCHACTION_MOVE
:
801 case MATCHACTION_DELETE
:
802 case MATCHACTION_STOP
:
803 case MATCHACTION_MARK_AS_SPAM
:
804 return TRUE
; /* MsgInfo invalid for message */
810 static gboolean
filter_msginfo(GSList
* filtering_list
, MsgInfo
* info
, PrefsAccount
* ac_prefs
)
816 cm_return_val_if_fail(info
!= NULL
, TRUE
);
818 for (l
= filtering_list
, final
= FALSE
, apply_next
= FALSE
; l
!= NULL
; l
= g_slist_next(l
)) {
819 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
821 if (filtering
->enabled
) {
822 if (debug_filtering_session
) {
823 gchar
*buf
= filteringprop_to_string(filtering
);
824 if (filtering
->name
&& *filtering
->name
!= '\0') {
825 log_print(LOG_DEBUG_FILTERING
,
826 _("processing rule '%s' [ %s ]\n"),
827 filtering
->name
, buf
);
829 log_print(LOG_DEBUG_FILTERING
,
830 _("processing rule <unnamed> [ %s ]\n"),
836 if (filtering_match_condition(filtering
, info
, ac_prefs
)) {
837 apply_next
= filtering_apply_rule(filtering
, info
, &final
);
843 if (debug_filtering_session
) {
844 gchar
*buf
= filteringprop_to_string(filtering
);
845 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
846 if (filtering
->name
&& *filtering
->name
!= '\0') {
847 log_status_skip(LOG_DEBUG_FILTERING
,
848 _("disabled rule '%s' [ %s ]\n"),
849 filtering
->name
, buf
);
851 log_status_skip(LOG_DEBUG_FILTERING
,
852 _("disabled rule <unnamed> [ %s ]\n"),
861 /* put in inbox if the last rule was not a final one, or
862 * a final rule could not be applied.
863 * Either of these cases is likely. */
864 if (!final
|| !apply_next
) {
872 *\brief Filter a message against a list of rules.
874 *\param flist List of filter rules.
875 *\param info Message.
877 *\return gboolean TRUE if filter rules handled the message.
879 *\note Returning FALSE means the message was not handled,
880 * and that the calling code should do the default
881 * processing. E.g. \ref inc.c::inc_start moves the
882 * message to the inbox.
884 gboolean
filter_message_by_msginfo(GSList
*flist
, MsgInfo
*info
, PrefsAccount
* ac_prefs
,
885 FilteringInvocationType context
, gchar
*extra_info
)
889 if (prefs_common
.enable_filtering_debug
) {
890 gchar
*tmp
= _("undetermined");
893 case FILTERING_INCORPORATION
:
894 tmp
= _("incorporation");
895 debug_filtering_session
= prefs_common
.enable_filtering_debug_inc
;
897 case FILTERING_MANUALLY
:
899 debug_filtering_session
= prefs_common
.enable_filtering_debug_manual
;
901 case FILTERING_FOLDER_PROCESSING
:
902 tmp
= _("folder processing");
903 debug_filtering_session
= prefs_common
.enable_filtering_debug_folder_proc
;
905 case FILTERING_PRE_PROCESSING
:
906 tmp
= _("pre-processing");
907 debug_filtering_session
= prefs_common
.enable_filtering_debug_pre_proc
;
909 case FILTERING_POST_PROCESSING
:
910 tmp
= _("post-processing");
911 debug_filtering_session
= prefs_common
.enable_filtering_debug_post_proc
;
914 debug_filtering_session
= FALSE
;
918 debug_filtering_session
= FALSE
;
920 if (debug_filtering_session
) {
921 gchar
*file
= procmsg_get_message_file_path(info
);
922 gchar
*spc
= g_strnfill(LOG_TIME_LEN
+ 1, ' ');
924 /* show context info and essential info about the message */
925 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
926 log_print(LOG_DEBUG_FILTERING
,
927 _("filtering message (%s%s%s)\n"
928 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
929 tmp
, extra_info
? _(": ") : "", extra_info
? extra_info
: "",
930 spc
, file
, spc
, prefs_common_translated_header_name("Date:"), info
->date
,
931 spc
, prefs_common_translated_header_name("From:"), info
->from
,
932 spc
, prefs_common_translated_header_name("To:"), info
->to
,
933 spc
, prefs_common_translated_header_name("Subject:"), info
->subject
);
935 log_print(LOG_DEBUG_FILTERING
,
936 _("filtering message (%s%s%s)\n"
937 "%smessage file: %s\n"),
938 tmp
, extra_info
? _(": ") : "", extra_info
? extra_info
: "",
945 debug_filtering_session
= FALSE
;
947 ret
= filter_msginfo(flist
, info
, ac_prefs
);
948 debug_filtering_session
= FALSE
;
952 gchar
*filteringaction_to_string(FilteringAction
*action
)
954 const gchar
*command_str
;
956 gchar
* quoted_header
;
957 GString
*dest
= g_string_new("");
958 gchar
*deststr
= NULL
;
960 command_str
= get_matchparser_tab_str(action
->type
);
962 if (command_str
== NULL
)
965 switch(action
->type
) {
966 case MATCHACTION_MOVE
:
967 case MATCHACTION_COPY
:
968 case MATCHACTION_EXECUTE
:
969 case MATCHACTION_SET_TAG
:
970 case MATCHACTION_UNSET_TAG
:
971 quoted_dest
= matcher_quote_str(action
->destination
);
972 g_string_append_printf(dest
, "%s \"%s\"", command_str
, quoted_dest
);
976 case MATCHACTION_DELETE
:
977 case MATCHACTION_MARK
:
978 case MATCHACTION_UNMARK
:
979 case MATCHACTION_LOCK
:
980 case MATCHACTION_UNLOCK
:
981 case MATCHACTION_MARK_AS_READ
:
982 case MATCHACTION_MARK_AS_UNREAD
:
983 case MATCHACTION_MARK_AS_SPAM
:
984 case MATCHACTION_MARK_AS_HAM
:
985 case MATCHACTION_STOP
:
986 case MATCHACTION_HIDE
:
987 case MATCHACTION_IGNORE
:
988 case MATCHACTION_WATCH
:
989 case MATCHACTION_CLEAR_TAGS
:
990 g_string_append_printf(dest
, "%s", command_str
);
993 case MATCHACTION_REDIRECT
:
994 case MATCHACTION_FORWARD
:
995 case MATCHACTION_FORWARD_AS_ATTACHMENT
:
996 quoted_dest
= matcher_quote_str(action
->destination
);
997 g_string_append_printf(dest
, "%s %d \"%s\"", command_str
, action
->account_id
, quoted_dest
);
1001 case MATCHACTION_COLOR
:
1002 g_string_append_printf(dest
, "%s %d", command_str
, action
->labelcolor
);
1005 case MATCHACTION_CHANGE_SCORE
:
1006 case MATCHACTION_SET_SCORE
:
1007 g_string_append_printf(dest
, "%s %d", command_str
, action
->score
);
1010 case MATCHACTION_ADD_TO_ADDRESSBOOK
:
1011 quoted_header
= matcher_quote_str(action
->header
);
1012 quoted_dest
= matcher_quote_str(action
->destination
);
1013 g_string_append_printf(dest
, "%s \"%s\" \"%s\"", command_str
, quoted_header
, quoted_dest
);
1014 g_free(quoted_dest
);
1015 g_free(quoted_header
);
1021 deststr
= dest
->str
;
1022 g_string_free(dest
, FALSE
);
1026 gchar
* filteringaction_list_to_string(GSList
* action_list
)
1028 gchar
*action_list_str
;
1032 action_list_str
= NULL
;
1033 for (tmp
= action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
1035 FilteringAction
* action
;
1039 action_str
= filteringaction_to_string(action
);
1041 if (action_list_str
!= NULL
) {
1042 list_str
= g_strconcat(action_list_str
, " ", action_str
, NULL
);
1043 g_free(action_list_str
);
1046 list_str
= g_strdup(action_str
);
1049 action_list_str
= list_str
;
1052 return action_list_str
;
1055 gchar
* filteringprop_to_string(FilteringProp
* prop
)
1058 gchar
*action_list_str
;
1059 gchar
*filtering_str
;
1064 action_list_str
= filteringaction_list_to_string(prop
->action_list
);
1066 if (action_list_str
== NULL
)
1069 list_str
= matcherlist_to_string(prop
->matchers
);
1071 if (list_str
== NULL
) {
1072 g_free(action_list_str
);
1076 filtering_str
= g_strconcat(list_str
, " ", action_list_str
, NULL
);
1077 g_free(action_list_str
);
1080 return filtering_str
;
1083 static void prefs_filtering_free(GSList
* prefs_filtering
)
1085 while (prefs_filtering
!= NULL
) {
1086 FilteringProp
* filtering
= (FilteringProp
*)
1087 prefs_filtering
->data
;
1088 filteringprop_free(filtering
);
1089 prefs_filtering
= g_slist_remove(prefs_filtering
, filtering
);
1093 static gboolean
prefs_filtering_free_func(GNode
*node
, gpointer data
)
1095 FolderItem
*item
= node
->data
;
1097 cm_return_val_if_fail(item
, FALSE
);
1098 cm_return_val_if_fail(item
->prefs
, FALSE
);
1100 prefs_filtering_free(item
->prefs
->processing
);
1101 item
->prefs
->processing
= NULL
;
1106 void prefs_filtering_clear(void)
1110 for (cur
= folder_get_list() ; cur
!= NULL
; cur
= g_list_next(cur
)) {
1113 folder
= (Folder
*) cur
->data
;
1114 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1115 prefs_filtering_free_func
, NULL
);
1118 prefs_filtering_free(filtering_rules
);
1119 filtering_rules
= NULL
;
1120 prefs_filtering_free(pre_global_processing
);
1121 pre_global_processing
= NULL
;
1122 prefs_filtering_free(post_global_processing
);
1123 post_global_processing
= NULL
;
1126 void prefs_filtering_clear_folder(Folder
*folder
)
1128 cm_return_if_fail(folder
);
1129 cm_return_if_fail(folder
->node
);
1131 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1132 prefs_filtering_free_func
, NULL
);
1133 /* FIXME: Note folder settings were changed, where the updates? */
1136 gboolean
filtering_peek_per_account_rules(GSList
*filtering_list
)
1137 /* return TRUE if there's at least one per-account filtering rule */
1141 for (l
= filtering_list
; l
!= NULL
; l
= g_slist_next(l
)) {
1142 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
1144 if (filtering
->enabled
&& (filtering
->account_id
!= 0)) {
1152 gboolean
filtering_action_list_rename_path(GSList
*action_list
, const gchar
*old_path
,
1153 const gchar
*new_path
)
1159 gchar
*old_path_with_sep
;
1163 GSList
* action_cur
;
1164 const gchar
*separator
=G_DIR_SEPARATOR_S
;
1165 gboolean matched
= FALSE
;
1169 oldpathlen
= strlen(old_path
);
1170 old_path_with_sep
= g_strconcat(old_path
,separator
,NULL
);
1172 for(action_cur
= action_list
; action_cur
!= NULL
;
1173 action_cur
= action_cur
->next
) {
1175 FilteringAction
*action
= action_cur
->data
;
1177 if (action
->type
== MATCHACTION_SET_TAG
||
1178 action
->type
== MATCHACTION_UNSET_TAG
)
1180 if (!action
->destination
)
1183 destlen
= strlen(action
->destination
);
1185 if (destlen
> oldpathlen
) {
1186 prefixlen
= destlen
- oldpathlen
;
1187 suffix
= action
->destination
+ prefixlen
;
1189 if (!strncmp(old_path
, suffix
, oldpathlen
)) {
1190 prefix
= g_malloc0(prefixlen
+ 1);
1191 strncpy2(prefix
, action
->destination
, prefixlen
);
1193 base
= suffix
+ oldpathlen
;
1194 while (*base
== G_DIR_SEPARATOR
) base
++;
1196 dest_path
= g_strconcat(prefix
, separator
,
1199 dest_path
= g_strconcat(prefix
,
1206 g_free(action
->destination
);
1207 action
->destination
= dest_path
;
1209 } else { /* for non-leaf folders */
1210 /* compare with trailing slash */
1211 if (!strncmp(old_path_with_sep
, action
->destination
, oldpathlen
+1)) {
1213 suffix
= action
->destination
+ oldpathlen
+ 1;
1214 dest_path
= g_strconcat(new_path
, separator
,
1216 g_free(action
->destination
);
1217 action
->destination
= dest_path
;
1222 /* folder-moving a leaf */
1223 if (!strcmp(old_path
, action
->destination
)) {
1224 dest_path
= g_strdup(new_path
);
1225 g_free(action
->destination
);
1226 action
->destination
= dest_path
;
1232 g_free(old_path_with_sep
);
1234 if (!strcmp(separator
, G_DIR_SEPARATOR_S
) && !matched
) {