2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2020 the Claws Mail Team and Hiroyuki Yamamoto
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_ALT_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 FilteringAction
* filteringaction_new(int type
, int account_id
,
62 gint labelcolor
, gint score
, gchar
* header
)
64 FilteringAction
* action
;
66 action
= g_new0(FilteringAction
, 1);
69 action
->account_id
= account_id
;
71 action
->destination
= g_strdup(destination
);
73 action
->destination
= NULL
;
76 action
->header
= g_strdup(header
);
78 action
->header
= NULL
;
80 action
->labelcolor
= labelcolor
;
81 action
->score
= score
;
85 void filteringaction_free(FilteringAction
* action
)
87 cm_return_if_fail(action
);
88 g_free(action
->header
);
89 g_free(action
->destination
);
93 static gint
action_list_sort(gconstpointer a
, gconstpointer b
)
95 int first
= filtering_is_final_action((FilteringAction
*) a
) ? 1 : 0;
96 int second
= filtering_is_final_action((FilteringAction
*) b
) ? 1 : 0;
98 return (first
- second
);
101 GSList
*filtering_action_list_sort(GSList
*action_list
)
103 return g_slist_sort(action_list
, action_list_sort
);
106 FilteringProp
* filteringprop_new(gboolean enabled
,
109 MatcherList
* matchers
,
110 GSList
* action_list
)
112 FilteringProp
* filtering
;
114 filtering
= g_new0(FilteringProp
, 1);
115 filtering
->enabled
= enabled
;
116 filtering
->name
= name
? g_strdup(name
): NULL
;
117 filtering
->account_id
= account_id
;
118 filtering
->matchers
= matchers
;
119 filtering
->action_list
= filtering_action_list_sort(action_list
);
124 static FilteringAction
* filteringaction_copy(FilteringAction
* src
)
126 FilteringAction
* new;
128 new = g_new0(FilteringAction
, 1);
130 new->type
= src
->type
;
131 new->account_id
= src
->account_id
;
132 if (src
->destination
)
133 new->destination
= g_strdup(src
->destination
);
135 new->destination
= NULL
;
136 new->labelcolor
= src
->labelcolor
;
137 new->score
= src
->score
;
142 FilteringProp
* filteringprop_copy(FilteringProp
*src
)
147 new = g_new0(FilteringProp
, 1);
148 new->matchers
= g_new0(MatcherList
, 1);
150 for (tmp
= src
->matchers
->matchers
; tmp
!= NULL
&& tmp
->data
!= NULL
;) {
151 MatcherProp
*matcher
= (MatcherProp
*)tmp
->data
;
153 new->matchers
->matchers
= g_slist_append(new->matchers
->matchers
,
154 matcherprop_copy(matcher
));
158 new->matchers
->bool_and
= src
->matchers
->bool_and
;
160 new->action_list
= NULL
;
162 for (tmp
= src
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
163 FilteringAction
*filtering_action
;
165 filtering_action
= tmp
->data
;
167 new->action_list
= g_slist_append(new->action_list
,
168 filteringaction_copy(filtering_action
));
171 new->enabled
= src
->enabled
;
172 new->name
= g_strdup(src
->name
);
177 void filteringprop_free(FilteringProp
* prop
)
181 cm_return_if_fail(prop
);
182 matcherlist_free(prop
->matchers
);
184 for (tmp
= prop
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
185 filteringaction_free(tmp
->data
);
187 g_slist_free(prop
->action_list
);
192 /* move and copy messages by batches to be faster on IMAP */
193 void filtering_move_and_copy_msgs(GSList
*msgs
)
195 GSList
*messages
= g_slist_copy(msgs
);
196 FolderItem
*last_item
= NULL
;
197 FiltOp cur_op
= IS_NOTHING
;
199 debug_print("checking %d messages\n", g_slist_length(msgs
));
201 GSList
*batch
= NULL
, *cur
;
203 for (cur
= messages
; cur
; cur
= cur
->next
) {
204 MsgInfo
*info
= (MsgInfo
*)cur
->data
;
205 if (last_item
== NULL
) {
206 if (info
->filter_op
== IS_COPY
|| info
->filter_op
== IS_MOVE
)
207 last_item
= info
->to_filter_folder
;
208 else if (info
->filter_op
== IS_DELE
)
209 last_item
= info
->folder
;
211 if (last_item
== NULL
)
213 if (cur_op
== IS_NOTHING
) {
214 if (info
->filter_op
== IS_COPY
)
216 else if (info
->filter_op
== IS_MOVE
)
218 else if (info
->filter_op
== IS_DELE
)
221 if (info
->filter_op
== IS_COPY
|| info
->filter_op
== IS_MOVE
) {
222 if (info
->to_filter_folder
== last_item
223 && cur_op
== info
->filter_op
) {
225 batch
= g_slist_prepend(batch
, info
);
227 } else if (info
->filter_op
== IS_DELE
) {
228 if (info
->folder
== last_item
229 && cur_op
== info
->filter_op
) {
231 batch
= g_slist_prepend(batch
, info
);
236 debug_print("no more messages to move/copy/del\n");
239 debug_print("%d messages to %s in %s\n", found
,
240 cur_op
==IS_COPY
? "copy":(cur_op
==IS_DELE
?"delete":"move"),
241 last_item
->name
? last_item
->name
:"(noname)");
243 for (cur
= batch
; cur
; cur
= cur
->next
) {
244 MsgInfo
*info
= (MsgInfo
*)cur
->data
;
245 messages
= g_slist_remove(messages
, info
);
246 info
->to_filter_folder
= NULL
;
247 info
->filter_op
= IS_NOTHING
;
249 batch
= g_slist_reverse(batch
);
250 if (g_slist_length(batch
)) {
251 MsgInfo
*info
= (MsgInfo
*)batch
->data
;
252 if (cur_op
== IS_COPY
&& last_item
!= info
->folder
) {
253 folder_item_copy_msgs(last_item
, batch
);
254 } else if (cur_op
== IS_MOVE
&& last_item
!= info
->folder
) {
255 if (folder_item_move_msgs(last_item
, batch
) < 0)
256 folder_item_move_msgs(
257 folder_get_default_inbox(),
259 } else if (cur_op
== IS_DELE
&& last_item
== info
->folder
) {
260 folder_item_remove_msgs(last_item
, batch
);
262 /* we don't reference the msginfos, because caller will do */
263 if (prefs_common
.real_time_sync
)
264 folder_item_synchronise(last_item
);
272 /* we don't reference the msginfos, because caller will do */
273 g_slist_free(messages
);
277 fitleringaction_apply
278 runs the action on one MsgInfo
279 return value : return TRUE if the action could be applied
282 #define FLUSH_COPY_IF_NEEDED(info) { \
283 if (info->filter_op == IS_COPY && info->to_filter_folder) { \
284 debug_print("must debatch pending copy\n"); \
285 folder_item_copy_msg(info->to_filter_folder, info); \
286 info->filter_op = IS_NOTHING; \
290 static gboolean
filteringaction_apply(FilteringAction
* action
, MsgInfo
* info
)
292 FolderItem
* dest_folder
;
295 PrefsAccount
* account
;
298 switch(action
->type
) {
299 case MATCHACTION_MOVE
:
300 if (MSG_IS_LOCKED(info
->flags
))
304 folder_find_item_from_identifier(action
->destination
);
306 debug_print("*** folder not found '%s'\n",
307 action
->destination
?action
->destination
:"(null)");
311 FLUSH_COPY_IF_NEEDED(info
);
312 /* mark message to be moved */
313 info
->filter_op
= IS_MOVE
;
314 info
->to_filter_folder
= dest_folder
;
317 case MATCHACTION_COPY
:
319 folder_find_item_from_identifier(action
->destination
);
322 debug_print("*** folder not found '%s'\n",
323 action
->destination
?action
->destination
:"(null)");
327 FLUSH_COPY_IF_NEEDED(info
);
328 /* mark message to be copied */
329 info
->filter_op
= IS_COPY
;
330 info
->to_filter_folder
= dest_folder
;
333 case MATCHACTION_SET_TAG
:
334 case MATCHACTION_UNSET_TAG
:
335 val
= tags_get_id_for_str(action
->destination
);
337 debug_print("*** tag '%s' not found\n",
338 action
->destination
?action
->destination
:"(null)");
341 FLUSH_COPY_IF_NEEDED(info
);
342 procmsg_msginfo_update_tags(info
, (action
->type
== MATCHACTION_SET_TAG
), val
);
345 case MATCHACTION_CLEAR_TAGS
:
346 FLUSH_COPY_IF_NEEDED(info
);
347 procmsg_msginfo_clear_tags(info
);
350 case MATCHACTION_DELETE
:
351 FLUSH_COPY_IF_NEEDED(info
);
352 info
->filter_op
= IS_DELE
;
355 case MATCHACTION_MARK
:
356 FLUSH_COPY_IF_NEEDED(info
);
357 procmsg_msginfo_set_flags(info
, MSG_MARKED
, 0);
360 case MATCHACTION_UNMARK
:
361 FLUSH_COPY_IF_NEEDED(info
);
362 procmsg_msginfo_unset_flags(info
, MSG_MARKED
, 0);
365 case MATCHACTION_LOCK
:
366 FLUSH_COPY_IF_NEEDED(info
);
367 procmsg_msginfo_set_flags(info
, MSG_LOCKED
, 0);
370 case MATCHACTION_UNLOCK
:
371 FLUSH_COPY_IF_NEEDED(info
);
372 procmsg_msginfo_unset_flags(info
, MSG_LOCKED
, 0);
375 case MATCHACTION_MARK_AS_READ
:
376 FLUSH_COPY_IF_NEEDED(info
);
377 procmsg_msginfo_unset_flags(info
, MSG_UNREAD
| MSG_NEW
, 0);
380 case MATCHACTION_MARK_AS_UNREAD
:
381 FLUSH_COPY_IF_NEEDED(info
);
382 procmsg_msginfo_change_flags(info
, MSG_UNREAD
, 0, MSG_NEW
, 0);
385 case MATCHACTION_MARK_AS_SPAM
:
386 FLUSH_COPY_IF_NEEDED(info
);
387 procmsg_spam_learner_learn(info
, NULL
, TRUE
);
388 procmsg_msginfo_change_flags(info
, MSG_SPAM
, 0, MSG_NEW
|MSG_UNREAD
, 0);
391 case MATCHACTION_MARK_AS_HAM
:
392 FLUSH_COPY_IF_NEEDED(info
);
393 procmsg_spam_learner_learn(info
, NULL
, FALSE
);
394 procmsg_msginfo_unset_flags(info
, MSG_SPAM
, 0);
397 case MATCHACTION_COLOR
:
398 FLUSH_COPY_IF_NEEDED(info
);
399 procmsg_msginfo_unset_flags(info
, MSG_CLABEL_FLAG_MASK
, 0);
400 procmsg_msginfo_set_flags(info
, MSG_COLORLABEL_TO_FLAGS(action
->labelcolor
), 0);
403 case MATCHACTION_FORWARD
:
404 case MATCHACTION_FORWARD_AS_ATTACHMENT
:
405 account
= account_find_from_id(action
->account_id
);
406 compose
= compose_forward(account
, info
,
407 action
->type
== MATCHACTION_FORWARD
? FALSE
: TRUE
,
409 compose_entry_append(compose
, action
->destination
,
410 compose
->account
->protocol
== A_NNTP
412 : COMPOSE_TO
, PREF_NONE
);
414 val
= compose_send(compose
);
416 return val
== 0 ? TRUE
: FALSE
;
418 case MATCHACTION_REDIRECT
:
419 account
= account_find_from_id(action
->account_id
);
420 compose
= compose_redirect(account
, info
, TRUE
);
421 if (compose
->account
->protocol
== A_NNTP
)
424 compose_entry_append(compose
, action
->destination
,
425 COMPOSE_TO
, PREF_NONE
);
427 val
= compose_send(compose
);
429 return val
== 0 ? TRUE
: FALSE
;
431 case MATCHACTION_EXECUTE
:
432 cmd
= matching_build_command(action
->destination
, info
);
436 if (system(cmd
) == -1)
437 g_warning("couldn't run %s", cmd
);
442 case MATCHACTION_SET_SCORE
:
443 FLUSH_COPY_IF_NEEDED(info
);
444 info
->score
= action
->score
;
447 case MATCHACTION_CHANGE_SCORE
:
448 FLUSH_COPY_IF_NEEDED(info
);
449 info
->score
+= action
->score
;
452 case MATCHACTION_STOP
:
455 case MATCHACTION_HIDE
:
456 FLUSH_COPY_IF_NEEDED(info
);
460 case MATCHACTION_IGNORE
:
461 FLUSH_COPY_IF_NEEDED(info
);
462 procmsg_msginfo_set_flags(info
, MSG_IGNORE_THREAD
, 0);
465 case MATCHACTION_WATCH
:
466 FLUSH_COPY_IF_NEEDED(info
);
467 procmsg_msginfo_set_flags(info
, MSG_WATCH_THREAD
, 0);
470 case MATCHACTION_ADD_TO_ADDRESSBOOK
:
472 #ifndef USE_ALT_ADDRBOOK
473 AddressDataSource
*book
= NULL
;
474 AddressBookFile
*abf
= NULL
;
475 ItemFolder
*folder
= NULL
;
478 Header
*header
= NULL
;
481 #ifndef USE_ALT_ADDRBOOK
482 if (!addressbook_peek_folder_exists(action
->destination
, &book
, &folder
)) {
483 g_warning("addressbook folder not found '%s'", action
->destination
?action
->destination
:"(null)");
487 g_warning("addressbook_peek_folder_exists returned NULL book");
491 abf
= book
->rawDataSource
;
494 if (procheader_get_header_from_msginfo(info
, &buf
, action
->header
) < 0)
497 header
= procheader_parse_header(buf
);
500 /* add all addresses that are not already in */
501 if (header
&& *header
->body
&& (*header
->body
!= '\0')) {
502 GSList
*address_list
= NULL
;
506 if (action
->destination
== NULL
||
507 strcasecmp(action
->destination
, "Any") == 0 ||
508 *(action
->destination
) == '\0')
511 path
= action
->destination
;
512 start_address_completion(path
);
514 address_list
= g_slist_append(address_list
, header
->body
);
515 for (walk
= address_list
; walk
!= NULL
; walk
= walk
->next
) {
516 gchar
*stripped_addr
= g_strdup(walk
->data
);
517 extract_address(stripped_addr
);
519 if (complete_matches_found(walk
->data
) == 0) {
520 gchar
*name
= procheader_get_fromname(walk
->data
);
521 debug_print("adding '%s <%s>' to addressbook '%s'\n",
522 name
, stripped_addr
, action
->destination
);
523 #ifndef USE_ALT_ADDRBOOK
524 if (!addrbook_add_contact(abf
, folder
, name
, stripped_addr
, NULL
)) {
526 if (!addressadd_selection(name
, stripped_addr
, NULL
, NULL
)) {
528 g_warning("contact could not be added");
533 debug_print("address '%s' already found in addressbook '%s', skipping\n",
534 stripped_addr
, action
->destination
);
536 g_free(stripped_addr
);
539 g_slist_free(address_list
);
540 end_address_completion();
542 g_warning("header '%s' not set or empty", action
->header
?action
->header
:"(null)");
545 procheader_header_free(header
);
546 return (errors
== 0);
554 gboolean
filteringaction_apply_action_list(GSList
*action_list
, MsgInfo
*info
)
557 cm_return_val_if_fail(action_list
, FALSE
);
558 cm_return_val_if_fail(info
, FALSE
);
559 for (p
= action_list
; p
&& p
->data
; p
= g_slist_next(p
)) {
560 FilteringAction
*a
= (FilteringAction
*) p
->data
;
561 if (filteringaction_apply(a
, info
)) {
562 if (filtering_is_final_action(a
))
571 static gboolean
filtering_match_condition(FilteringProp
*filtering
, MsgInfo
*info
,
572 PrefsAccount
*ac_prefs
)
574 /* this function returns true if a filtering rule applies regarding to its account
575 data and if it does, if the conditions list match.
577 per-account data of a filtering rule is either matched against current account
578 when filtering is done manually, or against the account currently used for
579 retrieving messages when it's an manual or automatic fetching of messages.
580 per-account data match doesn't apply to pre-/post-/folder-processing rules.
582 when filtering messages manually:
583 - either the filtering rule is not account-based and it will be processed
584 - or it's per-account and we check if we HAVE TO match it against the current
585 account (according to user-land settings, per-account rules might have to
586 be skipped, or only the rules that match the current account have to be
587 applied, or all rules will have to be applied regardless to if they are
588 account-based or not)
590 notes about debugging output in that function:
591 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
592 no debug output is done when filtering_debug_level is low
595 gboolean matches
= FALSE
;
597 if (ac_prefs
!= NULL
) {
598 matches
= ((filtering
->account_id
== 0)
599 || (filtering
->account_id
== ac_prefs
->account_id
));
602 if (debug_filtering_session
) {
604 /* either because rule is not account-based */
605 if (filtering
->account_id
== 0) {
606 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
607 log_status_ok(LOG_DEBUG_FILTERING
,
608 _("rule is not account-based\n"));
611 /* or because it is account-based, and matching current account */
612 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
613 /* with fewer detail when rule is OK */
614 log_status_ok(LOG_DEBUG_FILTERING
,
615 _("rule is account-based, "
616 "matching the account currently used to retrieve messages\n"));
618 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_HIGH
) {
619 /* with more detail when rule is OK */
620 log_status_ok(LOG_DEBUG_FILTERING
,
621 _("rule is account-based [id=%d, name='%s'], "
622 "matching the account currently used to retrieve messages\n"),
623 ac_prefs
->account_id
, ac_prefs
?ac_prefs
->account_name
:_("NON_EXISTENT"));
628 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
629 /* with fewer detail when rule is skipped */
630 log_status_skip(LOG_DEBUG_FILTERING
,
631 _("rule is account-based, "
632 "not matching the account currently used to retrieve messages\n"));
634 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_HIGH
) {
635 /* with more detail when rule is skipped */
636 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
638 log_status_skip(LOG_DEBUG_FILTERING
,
639 _("rule is account-based [id=%d, name='%s'], "
640 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
641 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
642 ac_prefs
->account_id
, ac_prefs
?ac_prefs
->account_name
:_("NON_EXISTENT"));
648 switch (prefs_common
.apply_per_account_filtering_rules
) {
649 case FILTERING_ACCOUNT_RULES_FORCE
:
650 /* apply filtering rules regardless to the account info */
654 if (debug_filtering_session
) {
655 if (filtering
->account_id
== 0) {
656 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
657 log_status_ok(LOG_DEBUG_FILTERING
,
658 _("rule is not account-based, "
659 "but all rules are applied on user request anyway\n"));
662 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
663 /* with fewer detail when rule is OK */
664 log_status_ok(LOG_DEBUG_FILTERING
,
665 _("rule is account-based, "
666 "but all rules are applied on user request anyway\n"));
668 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
669 /* with more detail when rule is OK */
670 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
672 log_status_ok(LOG_DEBUG_FILTERING
,
673 _("rule is account-based [id=%d, name='%s'], "
674 "but all rules are applied on user request anyway\n"),
675 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"));
681 case FILTERING_ACCOUNT_RULES_SKIP
:
682 /* don't apply filtering rules that belong to an account */
683 matches
= (filtering
->account_id
== 0);
686 if (debug_filtering_session
) {
688 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
689 log_status_ok(LOG_DEBUG_FILTERING
,
690 _("rule is not account-based\n"));
693 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
694 /* with fewer detail when rule is skipped */
695 log_status_skip(LOG_DEBUG_FILTERING
,
696 _("rule is account-based, "
697 "skipped on user request\n"));
699 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_HIGH
) {
700 /* with more detail when rule is skipped */
701 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
703 log_status_skip(LOG_DEBUG_FILTERING
,
704 _("rule is account-based [id=%d, name='%s'], "
705 "skipped on user request\n"),
706 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"));
712 case FILTERING_ACCOUNT_RULES_USE_CURRENT
:
713 matches
= ((filtering
->account_id
== 0)
714 || (filtering
->account_id
== cur_account
->account_id
));
717 if (debug_filtering_session
) {
719 if (filtering
->account_id
== 0) {
720 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
721 log_status_ok(LOG_DEBUG_FILTERING
,
722 _("rule is not account-based\n"));
725 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
726 /* with fewer detail when rule is OK */
727 log_status_ok(LOG_DEBUG_FILTERING
,
728 _("rule is account-based, "
729 "matching current account\n"));
731 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
732 /* with more detail when rule is OK */
733 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
735 log_status_ok(LOG_DEBUG_FILTERING
,
736 _("rule is account-based [id=%d, name='%s'], "
737 "matching current account [id=%d, name='%s']\n"),
738 account
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
739 cur_account
->account_id
, cur_account
?cur_account
->account_name
:_("NON_EXISTENT"));
744 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_MED
) {
745 /* with fewer detail when rule is skipped */
746 log_status_skip(LOG_DEBUG_FILTERING
,
747 _("rule is account-based, "
748 "not matching current account\n"));
750 if (prefs_common
.filtering_debug_level
== FILTERING_DEBUG_LEVEL_HIGH
) {
751 /* with more detail when rule is skipped */
752 PrefsAccount
*account
= account_find_from_id(filtering
->account_id
);
754 log_status_skip(LOG_DEBUG_FILTERING
,
755 _("rule is account-based [id=%d, name='%s'], "
756 "not matching current account [id=%d, name='%s']\n"),
757 filtering
->account_id
, account
?account
->account_name
:_("NON_EXISTENT"),
758 cur_account
->account_id
, cur_account
?cur_account
->account_name
:_("NON_EXISTENT"));
767 return matches
&& matcherlist_match(filtering
->matchers
, info
);
771 *\brief Apply a rule on message.
773 *\param filtering List of filtering rules.
774 *\param info Message to apply rules on.
775 *\param final Variable returning TRUE or FALSE if one of the
776 * encountered actions was final.
777 * See also \ref filtering_is_final_action.
779 *\return gboolean TRUE to continue applying rules.
781 static gboolean
filtering_apply_rule(FilteringProp
*filtering
, MsgInfo
*info
,
784 gboolean result
= TRUE
;
789 for (tmp
= filtering
->action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
790 FilteringAction
* action
;
793 buf
= filteringaction_to_string(action
);
794 if (debug_filtering_session
)
795 log_print(LOG_DEBUG_FILTERING
, _("applying action [ %s ]\n"), buf
);
797 if (FALSE
== (result
= filteringaction_apply(action
, info
))) {
798 if (debug_filtering_session
) {
799 if (action
->type
!= MATCHACTION_STOP
)
800 log_warning(LOG_DEBUG_FILTERING
, _("action could not apply\n"));
801 log_print(LOG_DEBUG_FILTERING
,
802 _("no further processing after action [ %s ]\n"), buf
);
804 debug_print("No further processing after rule %s\n", buf
);
807 if (filtering_is_final_action(action
)) {
817 *\brief Check if an action is "final", i.e. should break further
820 *\param filtering_action Action to check.
822 *\return gboolean TRUE if \a filtering_action is final.
824 static gboolean
filtering_is_final_action(FilteringAction
*filtering_action
)
826 switch(filtering_action
->type
) {
827 case MATCHACTION_MOVE
:
828 case MATCHACTION_DELETE
:
829 case MATCHACTION_STOP
:
830 return TRUE
; /* MsgInfo invalid for message */
836 gboolean
processing_enabled(GSList
*filtering_list
)
839 for (l
= filtering_list
; l
!= NULL
; l
= g_slist_next(l
)) {
840 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
841 if (filtering
->enabled
)
847 static gboolean
filter_msginfo(GSList
* filtering_list
, MsgInfo
* info
, PrefsAccount
* ac_prefs
)
853 cm_return_val_if_fail(info
!= NULL
, TRUE
);
855 for (l
= filtering_list
, final
= FALSE
, apply_next
= FALSE
; l
!= NULL
; l
= g_slist_next(l
)) {
856 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
858 if (filtering
->enabled
) {
859 if (debug_filtering_session
) {
860 gchar
*buf
= filteringprop_to_string(filtering
);
861 if (filtering
->name
&& *filtering
->name
!= '\0') {
862 log_print(LOG_DEBUG_FILTERING
,
863 _("processing rule '%s' [ %s ]\n"),
864 filtering
->name
, buf
);
866 log_print(LOG_DEBUG_FILTERING
,
867 _("processing rule <unnamed> [ %s ]\n"),
873 if (filtering_match_condition(filtering
, info
, ac_prefs
)) {
874 apply_next
= filtering_apply_rule(filtering
, info
, &final
);
880 if (debug_filtering_session
) {
881 gchar
*buf
= filteringprop_to_string(filtering
);
882 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
883 if (filtering
->name
&& *filtering
->name
!= '\0') {
884 log_status_skip(LOG_DEBUG_FILTERING
,
885 _("disabled rule '%s' [ %s ]\n"),
886 filtering
->name
, buf
);
888 log_status_skip(LOG_DEBUG_FILTERING
,
889 _("disabled rule <unnamed> [ %s ]\n"),
898 /* put in inbox if the last rule was not a final one, or
899 * a final rule could not be applied.
900 * Either of these cases is likely. */
901 if (!final
|| !apply_next
) {
909 *\brief Filter a message against a list of rules.
911 *\param flist List of filter rules.
912 *\param info Message.
914 *\return gboolean TRUE if filter rules handled the message.
916 *\note Returning FALSE means the message was not handled,
917 * and that the calling code should do the default
918 * processing. E.g. \ref inc.c::inc_start moves the
919 * message to the inbox.
921 gboolean
filter_message_by_msginfo(GSList
*flist
, MsgInfo
*info
, PrefsAccount
* ac_prefs
,
922 FilteringInvocationType context
, gchar
*extra_info
)
926 if (prefs_common
.enable_filtering_debug
) {
927 gchar
*tmp
= _("undetermined");
930 case FILTERING_INCORPORATION
:
931 tmp
= _("incorporation");
932 debug_filtering_session
= prefs_common
.enable_filtering_debug_inc
;
934 case FILTERING_MANUALLY
:
936 debug_filtering_session
= prefs_common
.enable_filtering_debug_manual
;
938 case FILTERING_FOLDER_PROCESSING
:
939 tmp
= _("folder processing");
940 debug_filtering_session
= prefs_common
.enable_filtering_debug_folder_proc
;
942 case FILTERING_PRE_PROCESSING
:
943 tmp
= _("pre-processing");
944 debug_filtering_session
= prefs_common
.enable_filtering_debug_pre_proc
;
946 case FILTERING_POST_PROCESSING
:
947 tmp
= _("post-processing");
948 debug_filtering_session
= prefs_common
.enable_filtering_debug_post_proc
;
951 debug_filtering_session
= FALSE
;
955 if (debug_filtering_session
) {
956 gchar
*file
= procmsg_get_message_file_path(info
);
957 gchar
*spc
= g_strnfill(LOG_TIME_LEN
+ 1, ' ');
959 /* show context info and essential info about the message */
960 if (prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_MED
) {
961 log_print(LOG_DEBUG_FILTERING
,
962 _("filtering message (%s%s%s)\n"
963 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
964 tmp
, extra_info
? _(": ") : "", extra_info
? extra_info
: "",
965 spc
, file
, spc
, prefs_common_translated_header_name("Date:"), info
->date
,
966 spc
, prefs_common_translated_header_name("From:"), info
->from
,
967 spc
, prefs_common_translated_header_name("To:"), info
->to
,
968 spc
, prefs_common_translated_header_name("Subject:"), info
->subject
);
970 log_print(LOG_DEBUG_FILTERING
,
971 _("filtering message (%s%s%s)\n"
972 "%smessage file: %s\n"),
973 tmp
, extra_info
? _(": ") : "", extra_info
? extra_info
: "",
980 debug_filtering_session
= FALSE
;
982 ret
= filter_msginfo(flist
, info
, ac_prefs
);
983 debug_filtering_session
= FALSE
;
987 gchar
*filteringaction_to_string(FilteringAction
*action
)
989 const gchar
*command_str
;
991 gchar
* quoted_header
;
992 GString
*dest
= g_string_new("");
993 gchar
*deststr
= NULL
;
995 command_str
= get_matchparser_tab_str(action
->type
);
997 if (command_str
== NULL
) {
998 g_string_free(dest
, TRUE
);
1002 switch(action
->type
) {
1003 case MATCHACTION_MOVE
:
1004 case MATCHACTION_COPY
:
1005 case MATCHACTION_EXECUTE
:
1006 case MATCHACTION_SET_TAG
:
1007 case MATCHACTION_UNSET_TAG
:
1008 quoted_dest
= matcher_quote_str(action
->destination
);
1009 g_string_append_printf(dest
, "%s \"%s\"", command_str
, quoted_dest
);
1010 g_free(quoted_dest
);
1013 case MATCHACTION_DELETE
:
1014 case MATCHACTION_MARK
:
1015 case MATCHACTION_UNMARK
:
1016 case MATCHACTION_LOCK
:
1017 case MATCHACTION_UNLOCK
:
1018 case MATCHACTION_MARK_AS_READ
:
1019 case MATCHACTION_MARK_AS_UNREAD
:
1020 case MATCHACTION_MARK_AS_SPAM
:
1021 case MATCHACTION_MARK_AS_HAM
:
1022 case MATCHACTION_STOP
:
1023 case MATCHACTION_HIDE
:
1024 case MATCHACTION_IGNORE
:
1025 case MATCHACTION_WATCH
:
1026 case MATCHACTION_CLEAR_TAGS
:
1027 g_string_append_printf(dest
, "%s", command_str
);
1030 case MATCHACTION_REDIRECT
:
1031 case MATCHACTION_FORWARD
:
1032 case MATCHACTION_FORWARD_AS_ATTACHMENT
:
1033 quoted_dest
= matcher_quote_str(action
->destination
);
1034 g_string_append_printf(dest
, "%s %d \"%s\"", command_str
, action
->account_id
, quoted_dest
);
1035 g_free(quoted_dest
);
1038 case MATCHACTION_COLOR
:
1039 g_string_append_printf(dest
, "%s %d", command_str
, action
->labelcolor
);
1042 case MATCHACTION_CHANGE_SCORE
:
1043 case MATCHACTION_SET_SCORE
:
1044 g_string_append_printf(dest
, "%s %d", command_str
, action
->score
);
1047 case MATCHACTION_ADD_TO_ADDRESSBOOK
:
1048 quoted_header
= matcher_quote_str(action
->header
);
1049 quoted_dest
= matcher_quote_str(action
->destination
);
1050 g_string_append_printf(dest
, "%s \"%s\" \"%s\"", command_str
, quoted_header
, quoted_dest
);
1051 g_free(quoted_dest
);
1052 g_free(quoted_header
);
1056 g_string_free(dest
, TRUE
);
1059 deststr
= dest
->str
;
1060 g_string_free(dest
, FALSE
);
1064 gchar
* filteringaction_list_to_string(GSList
* action_list
)
1066 gchar
*action_list_str
;
1070 action_list_str
= NULL
;
1071 for (tmp
= action_list
; tmp
!= NULL
; tmp
= tmp
->next
) {
1073 FilteringAction
* action
;
1077 action_str
= filteringaction_to_string(action
);
1079 if (action_list_str
!= NULL
) {
1080 list_str
= g_strconcat(action_list_str
, " ", action_str
, NULL
);
1081 g_free(action_list_str
);
1084 list_str
= g_strdup(action_str
);
1087 action_list_str
= list_str
;
1090 return action_list_str
;
1093 gchar
* filteringprop_to_string(FilteringProp
* prop
)
1096 gchar
*action_list_str
;
1097 gchar
*filtering_str
;
1102 action_list_str
= filteringaction_list_to_string(prop
->action_list
);
1104 if (action_list_str
== NULL
)
1107 list_str
= matcherlist_to_string(prop
->matchers
);
1109 if (list_str
== NULL
) {
1110 g_free(action_list_str
);
1114 filtering_str
= g_strconcat(list_str
, " ", action_list_str
, NULL
);
1115 g_free(action_list_str
);
1118 return filtering_str
;
1121 static void prefs_filtering_free(GSList
* prefs_filtering
)
1123 while (prefs_filtering
!= NULL
) {
1124 FilteringProp
* filtering
= (FilteringProp
*)
1125 prefs_filtering
->data
;
1126 prefs_filtering
= g_slist_remove(prefs_filtering
, filtering
);
1127 filteringprop_free(filtering
);
1131 static gboolean
prefs_filtering_free_func(GNode
*node
, gpointer data
)
1133 FolderItem
*item
= node
->data
;
1135 cm_return_val_if_fail(item
, FALSE
);
1136 cm_return_val_if_fail(item
->prefs
, FALSE
);
1138 prefs_filtering_free(item
->prefs
->processing
);
1139 item
->prefs
->processing
= NULL
;
1144 void prefs_filtering_clear(void)
1148 for (cur
= folder_get_list() ; cur
!= NULL
; cur
= g_list_next(cur
)) {
1151 folder
= (Folder
*) cur
->data
;
1152 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1153 prefs_filtering_free_func
, NULL
);
1156 prefs_filtering_free(filtering_rules
);
1157 filtering_rules
= NULL
;
1158 prefs_filtering_free(pre_global_processing
);
1159 pre_global_processing
= NULL
;
1160 prefs_filtering_free(post_global_processing
);
1161 post_global_processing
= NULL
;
1164 void prefs_filtering_clear_folder(Folder
*folder
)
1166 cm_return_if_fail(folder
);
1167 cm_return_if_fail(folder
->node
);
1169 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
1170 prefs_filtering_free_func
, NULL
);
1171 /* FIXME: Note folder settings were changed, where the updates? */
1174 gboolean
filtering_peek_per_account_rules(GSList
*filtering_list
)
1175 /* return TRUE if there's at least one per-account filtering rule */
1179 for (l
= filtering_list
; l
!= NULL
; l
= g_slist_next(l
)) {
1180 FilteringProp
* filtering
= (FilteringProp
*) l
->data
;
1182 if (filtering
->enabled
&& (filtering
->account_id
!= 0)) {
1190 gboolean
filtering_action_list_rename_path(GSList
*action_list
, const gchar
*old_path
,
1191 const gchar
*new_path
)
1197 gchar
*old_path_with_sep
;
1201 GSList
* action_cur
;
1202 const gchar
*separator
=G_DIR_SEPARATOR_S
;
1203 gboolean matched
= FALSE
;
1207 oldpathlen
= strlen(old_path
);
1208 old_path_with_sep
= g_strconcat(old_path
,separator
,NULL
);
1210 for(action_cur
= action_list
; action_cur
!= NULL
;
1211 action_cur
= action_cur
->next
) {
1213 FilteringAction
*action
= action_cur
->data
;
1215 if (action
->type
== MATCHACTION_SET_TAG
||
1216 action
->type
== MATCHACTION_UNSET_TAG
)
1218 if (!action
->destination
)
1221 destlen
= strlen(action
->destination
);
1223 if (destlen
> oldpathlen
) {
1224 prefixlen
= destlen
- oldpathlen
;
1225 suffix
= action
->destination
+ prefixlen
;
1227 if (!strncmp(old_path
, suffix
, oldpathlen
)) {
1228 prefix
= g_malloc0(prefixlen
+ 1);
1229 strncpy2(prefix
, action
->destination
, prefixlen
);
1231 base
= suffix
+ oldpathlen
;
1232 while (*base
== G_DIR_SEPARATOR
)
1235 dest_path
= g_strconcat(prefix
, separator
, new_path
, NULL
);
1237 dest_path
= g_strconcat(prefix
, separator
, new_path
, separator
, base
, NULL
);
1240 g_free(action
->destination
);
1241 action
->destination
= dest_path
;
1243 } else { /* for non-leaf folders */
1244 /* compare with trailing slash */
1245 if (!strncmp(old_path_with_sep
, action
->destination
, oldpathlen
+1)) {
1247 suffix
= action
->destination
+ oldpathlen
+ 1;
1248 dest_path
= g_strconcat(new_path
, separator
,
1250 g_free(action
->destination
);
1251 action
->destination
= dest_path
;
1256 /* folder-moving a leaf */
1257 if (!strcmp(old_path
, action
->destination
)) {
1258 dest_path
= g_strdup(new_path
);
1259 g_free(action
->destination
);
1260 action
->destination
= dest_path
;
1266 g_free(old_path_with_sep
);
1268 if (!strcmp(separator
, G_DIR_SEPARATOR_S
) && !matched
) {