c75d43d17c356d11f849a622700456fa931cfcc8
[claws.git] / src / filtering.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto & The Claws Mail Team
4  *
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 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #include "defs.h"
21 #include <glib.h>
22 #include <glib/gi18n.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <gtk/gtk.h>
28 #include <stdio.h>
29
30 #include "utils.h"
31 #include "procheader.h"
32 #include "matcher.h"
33 #include "filtering.h"
34 #include "prefs_gtk.h"
35 #include "compose.h"
36 #include "prefs_common.h"
37 #include "addrbook.h"
38 #include "addr_compl.h"
39 #include "log.h"
40
41 #define PREFSBUFSIZE            1024
42
43 GSList * pre_global_processing = NULL;
44 GSList * post_global_processing = NULL;
45 GSList * filtering_rules = NULL;
46
47 gboolean debug_filtering_session = FALSE;
48
49 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
50
51 #define STRLEN_WITH_CHECK(expr) \
52         strlen_with_check(#expr, __LINE__, expr)
53                 
54 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
55 {
56         if (str) 
57                 return strlen(str);
58         else {
59                 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
60                 return 0;
61         }
62 }
63
64 FilteringAction * filteringaction_new(int type, int account_id,
65                                       gchar * destination,
66                                       gint labelcolor, gint score, gchar * header)
67 {
68         FilteringAction * action;
69
70         action = g_new0(FilteringAction, 1);
71
72         action->type = type;
73         action->account_id = account_id;
74         if (destination) {
75                 action->destination       = g_strdup(destination);
76         } else {
77                 action->destination       = NULL;
78         }
79         if (header) {
80                 action->header    = g_strdup(header);
81         } else {
82                 action->header       = NULL;
83         }
84         action->labelcolor = labelcolor;        
85         action->score = score;
86         return action;
87 }
88
89 void filteringaction_free(FilteringAction * action)
90 {
91         g_return_if_fail(action);
92         g_free(action->header);
93         g_free(action->destination);
94         g_free(action);
95 }
96
97 FilteringProp * filteringprop_new(gboolean enabled,
98                                   const gchar *name,
99                                   gint account_id,
100                                   MatcherList * matchers,
101                                   GSList * action_list)
102 {
103         FilteringProp * filtering;
104
105         filtering = g_new0(FilteringProp, 1);
106         filtering->enabled = enabled;
107         filtering->name = name ? g_strdup(name): NULL;
108         filtering->account_id = account_id;
109         filtering->matchers = matchers;
110         filtering->action_list = action_list;
111
112         return filtering;
113 }
114
115 static FilteringAction * filteringaction_copy(FilteringAction * src)
116 {
117         FilteringAction * new;
118         
119         new = g_new0(FilteringAction, 1);
120         
121         new->type = src->type;
122         new->account_id = src->account_id;
123         if (src->destination)
124                 new->destination = g_strdup(src->destination);
125         else 
126                 new->destination = NULL;
127         new->labelcolor = src->labelcolor;
128         new->score = src->score;
129
130         return new;
131 }
132
133 FilteringProp * filteringprop_copy(FilteringProp *src)
134 {
135         FilteringProp * new;
136         GSList *tmp;
137         
138         new = g_new0(FilteringProp, 1);
139         new->matchers = g_new0(MatcherList, 1);
140
141         for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
142                 MatcherProp *matcher = (MatcherProp *)tmp->data;
143                 
144                 new->matchers->matchers = g_slist_append(new->matchers->matchers,
145                                                    matcherprop_copy(matcher));
146                 tmp = tmp->next;
147         }
148
149         new->matchers->bool_and = src->matchers->bool_and;
150
151         new->action_list = NULL;
152
153         for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
154                 FilteringAction *filtering_action;
155                 
156                 filtering_action = tmp->data;
157                 
158                 new->action_list = g_slist_append(new->action_list,
159                     filteringaction_copy(filtering_action));
160         }
161
162         new->enabled = src->enabled;
163         new->name = g_strdup(src->name);
164
165         return new;
166 }
167
168 void filteringprop_free(FilteringProp * prop)
169 {
170         GSList * tmp;
171
172         g_return_if_fail(prop);
173         matcherlist_free(prop->matchers);
174         
175         for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
176                 filteringaction_free(tmp->data);
177         }
178         g_free(prop->name);
179         g_free(prop);
180 }
181
182 void filtering_move_and_copy_msg(MsgInfo *msginfo)
183 {
184         GSList *list = g_slist_append(NULL, msginfo);
185         filtering_move_and_copy_msgs(list);
186         g_slist_free(list);
187 }
188
189 /* move and copy messages by batches to be faster on IMAP */
190 void filtering_move_and_copy_msgs(GSList *msgs)
191 {
192         GSList *messages = g_slist_copy(msgs);
193         FolderItem *last_item = NULL;
194         gboolean is_copy = FALSE, is_move = FALSE;
195         debug_print("checking %d messages\n", g_slist_length(msgs));
196         while (messages) {
197                 GSList *batch = NULL, *cur;
198                 gint found = 0;
199                 for (cur = messages; cur; cur = cur->next) {
200                         MsgInfo *info = (MsgInfo *)cur->data;
201                         if (last_item == NULL) {
202                                 last_item = info->to_filter_folder;
203                         }
204                         if (last_item == NULL)
205                                 continue;
206                         if (!is_copy && !is_move) {
207                                 if (info->is_copy)
208                                         is_copy = TRUE;
209                                 else if (info->is_move)
210                                         is_move = TRUE;
211                         }
212                         found++;
213                         if (info->to_filter_folder == last_item 
214                         &&  info->is_copy == is_copy
215                         &&  info->is_move == is_move) {
216                                 batch = g_slist_prepend(batch, info);
217                         }
218                 }
219                 if (found == 0) {
220                         debug_print("no more messages to move/copy\n");
221                         break;
222                 } else {
223                         debug_print("%d messages to %s in %s\n", found,
224                                 is_copy ? "copy":"move", last_item->name ? last_item->name:"(noname)");
225                 }
226                 for (cur = batch; cur; cur = cur->next) {
227                         MsgInfo *info = (MsgInfo *)cur->data;
228                         messages = g_slist_remove(messages, info);
229                 }
230                 batch = g_slist_reverse(batch);
231                 if (g_slist_length(batch)) {
232                         MsgInfo *info = (MsgInfo *)batch->data;
233                         if (is_copy && last_item != info->folder) {
234                                 folder_item_copy_msgs(last_item, batch);
235                         } else if (is_move && last_item != info->folder) {
236                                 if (folder_item_move_msgs(last_item, batch) < 0)
237                                         folder_item_move_msgs(
238                                                 folder_get_default_inbox(), 
239                                                 batch);
240                         }
241                         /* we don't reference the msginfos, because caller will do */
242                         g_slist_free(batch);
243                         batch = NULL;
244                 }
245                 last_item = NULL;
246                 is_copy = FALSE;
247                 is_move = FALSE;
248         }
249         /* we don't reference the msginfos, because caller will do */
250         g_slist_free(messages);
251 }
252
253 /*
254   fitleringaction_apply
255   runs the action on one MsgInfo
256   return value : return TRUE if the action could be applied
257 */
258
259 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
260 {
261         FolderItem * dest_folder;
262         gint val;
263         Compose * compose;
264         PrefsAccount * account;
265         gchar * cmd;
266
267         switch(action->type) {
268         case MATCHACTION_MOVE:
269                 dest_folder =
270                         folder_find_item_from_identifier(action->destination);
271                 if (!dest_folder) {
272                         debug_print("*** folder not found '%s'\n",
273                                 action->destination ?action->destination :"");
274                         return FALSE;
275                 }
276                 
277                 /* check if mail is set to copy already, 
278                  * in which case we have to do it */
279                 if (info->is_copy && info->to_filter_folder) {
280                         debug_print("should cp and mv !\n");
281                         folder_item_copy_msg(info->to_filter_folder, info);
282                         info->is_copy = FALSE;
283                 }
284                 /* mark message to be moved */          
285                 info->is_move = TRUE;
286                 info->to_filter_folder = dest_folder;
287                 return TRUE;
288
289         case MATCHACTION_COPY:
290                 dest_folder =
291                         folder_find_item_from_identifier(action->destination);
292
293                 if (!dest_folder) {
294                         debug_print("*** folder not found '%s'\n",
295                                 action->destination ?action->destination :"");
296                         return FALSE;
297                 }
298
299                 /* check if mail is set to copy already, 
300                  * in which case we have to do it */
301                 if (info->is_copy && info->to_filter_folder) {
302                         debug_print("should cp and mv !\n");
303                         folder_item_copy_msg(info->to_filter_folder, info);
304                         info->is_copy = FALSE;
305                 }
306                 /* mark message to be copied */         
307                 info->is_copy = TRUE;
308                 info->to_filter_folder = dest_folder;
309                 return TRUE;
310
311         case MATCHACTION_DELETE:
312                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
313                         return FALSE;
314                 return TRUE;
315
316         case MATCHACTION_MARK:
317                 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
318                 return TRUE;
319
320         case MATCHACTION_UNMARK:
321                 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
322                 return TRUE;
323
324         case MATCHACTION_LOCK:
325                 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
326                 return TRUE;
327
328         case MATCHACTION_UNLOCK:
329                 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);       
330                 return TRUE;
331                 
332         case MATCHACTION_MARK_AS_READ:
333                 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
334                 return TRUE;
335
336         case MATCHACTION_MARK_AS_UNREAD:
337                 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
338                 return TRUE;
339         
340         case MATCHACTION_MARK_AS_SPAM:
341                 procmsg_spam_learner_learn(info, NULL, TRUE);
342                 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
343                 if (procmsg_spam_get_folder(info)) {
344                         info->is_move = TRUE;
345                         info->to_filter_folder = procmsg_spam_get_folder(info);
346                 }
347                 return TRUE;
348
349         case MATCHACTION_MARK_AS_HAM:
350                 procmsg_spam_learner_learn(info, NULL, FALSE);
351                 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
352                 return TRUE;
353         
354         case MATCHACTION_COLOR:
355                 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0); 
356                 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
357                 return TRUE;
358
359         case MATCHACTION_FORWARD:
360         case MATCHACTION_FORWARD_AS_ATTACHMENT:
361                 account = account_find_from_id(action->account_id);
362                 compose = compose_forward(account, info,
363                         action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
364                         NULL, TRUE, TRUE);
365                 compose_entry_append(compose, action->destination,
366                                      compose->account->protocol == A_NNTP
367                                             ? COMPOSE_NEWSGROUPS
368                                             : COMPOSE_TO);
369
370                 val = compose_send(compose);
371
372                 return val == 0 ? TRUE : FALSE;
373
374         case MATCHACTION_REDIRECT:
375                 account = account_find_from_id(action->account_id);
376                 compose = compose_redirect(account, info, TRUE);
377                 if (compose->account->protocol == A_NNTP)
378                         break;
379                 else
380                         compose_entry_append(compose, action->destination,
381                                              COMPOSE_TO);
382
383                 val = compose_send(compose);
384                 
385                 return val == 0 ? TRUE : FALSE;
386
387         case MATCHACTION_EXECUTE:
388                 cmd = matching_build_command(action->destination, info);
389                 if (cmd == NULL)
390                         return FALSE;
391                 else {
392                         system(cmd);
393                         g_free(cmd);
394                 }
395                 return TRUE;
396
397         case MATCHACTION_SET_SCORE:
398                 info->score = action->score;
399                 return TRUE;
400
401         case MATCHACTION_CHANGE_SCORE:
402                 info->score += action->score;
403                 return TRUE;
404
405         case MATCHACTION_STOP:
406                 return FALSE;
407
408         case MATCHACTION_HIDE:
409                 info->hidden = TRUE;
410                 return TRUE;
411
412         case MATCHACTION_IGNORE:
413                 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
414                 return TRUE;
415
416         case MATCHACTION_ADD_TO_ADDRESSBOOK:
417                 {
418                         AddressDataSource *book = NULL;
419                         AddressBookFile *abf = NULL;
420                         ItemFolder *folder = NULL;
421                         gchar buf[BUFFSIZE];
422                         Header *header;
423                         gint errors = 0;
424
425                         if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
426                                 g_warning("addressbook folder not found '%s'\n", action->destination);
427                                 return FALSE;
428                         }
429                         if (!book) {
430                                 g_warning("addressbook_peek_folder_exists returned NULL book\n");
431                                 return FALSE;
432                         }
433
434                         abf = book->rawDataSource;
435
436                         /* get the header */
437                         procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
438                         header = procheader_parse_header(buf);
439
440                         /* add all addresses that are not already in */
441                         if (header && *header->body && (*header->body != '\0')) {
442                                 GSList *address_list = NULL;
443                                 GSList *walk = NULL;
444                                 gchar *path = NULL;
445
446                                 if (action->destination == NULL ||
447                                                 strcasecmp(action->destination, _("Any")) == 0 ||
448                                                 *(action->destination) == '\0')
449                                         path = NULL;
450                                 else
451                                         path = action->destination;
452                                 start_address_completion(path);
453
454                                 address_list = address_list_append(address_list, header->body);
455                                 for (walk = address_list; walk != NULL; walk = walk->next) {
456                                         gchar *stripped_addr = g_strdup(walk->data);
457                                         extract_address(stripped_addr);
458
459                                         if (complete_matches_found(walk->data) == 0) {
460                                                 debug_print("adding address '%s' to addressbook '%s'\n",
461                                                                 stripped_addr, action->destination);
462                                                 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
463                                                         g_warning("contact could not been added\n");
464                                                         errors++;
465                                                 }
466                                         } else {
467                                                 debug_print("address '%s' already found in addressbook '%s', skipping\n",
468                                                                 stripped_addr, action->destination);
469                                         }
470                                         g_free(stripped_addr);
471                                 }
472
473                                 g_slist_free(address_list);
474                                 end_address_completion();
475                         } else {
476                                 g_warning("header '%s' not set or empty\n", action->header);
477                         }
478                         return (errors == 0);
479                 }
480
481         default:
482                 break;
483         }
484         return FALSE;
485 }
486
487 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
488 {
489         GSList *p;
490         g_return_val_if_fail(action_list, FALSE);
491         g_return_val_if_fail(info, FALSE);
492         for (p = action_list; p && p->data; p = g_slist_next(p)) {
493                 FilteringAction *a = (FilteringAction *) p->data;
494                 if (filteringaction_apply(a, info)) {
495                         if (filtering_is_final_action(a))
496                                 break;
497                 } else
498                         return FALSE;
499                 
500         }
501         return TRUE;
502 }
503
504 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
505                                                         PrefsAccount *ac_prefs)
506
507 /* this function returns true if a filtering rule applies regarding to its account
508    data and if it does, if the conditions list match.
509
510    per-account data of a filtering rule is either matched against current account
511    when filtering is done manually, or against the account currently used for
512    retrieving messages when it's an manual or automatic fetching of messages.
513    per-account data match doesn't apply to pre-/post-/folder-processing rules.
514
515    when filtering messages manually:
516     - either the filtering rule is not account-based and it will be processed
517         - or it's per-account and we check if we HAVE TO match it against the current
518           account (according to user-land settings, per-account rules might have to
519           be skipped, or only the rules that match the current account have to be
520           applied, or all rules will have to be applied regardless to if they are
521           account-based or not)
522
523    notes about debugging output in that function:
524    when not matching, log_status_skip() is used, otherwise log_status_ok() is used
525    no debug output is done when filtering_debug_level is low
526 */
527 {
528         gboolean matches = FALSE;
529
530         if (ac_prefs != NULL) {
531                 matches = ((filtering->account_id == 0)
532                                         || (filtering->account_id == ac_prefs->account_id));
533
534                 /* debug output */
535                 if (debug_filtering_session) {
536                         if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
537                                 if (filtering->account_id == 0) {
538                                         log_status_ok(LOG_DEBUG_FILTERING,
539                                                         _("rule is not account-based\n"));
540                                 } else {
541                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
542                                                 log_status_ok(LOG_DEBUG_FILTERING,
543                                                                 _("rule is account-based [id=%d, name='%s'], "
544                                                                 "matching the account currently used to retrieve messages\n"),
545                                                                 ac_prefs->account_id, ac_prefs->account_name);
546                                         }
547                                 }
548                         }
549                         else
550                         if (!matches) {
551                                 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
552                                         log_status_skip(LOG_DEBUG_FILTERING,
553                                                         _("rule is account-based, "
554                                                         "not matching the account currently used to retrieve messages\n"));
555                                 } else {
556                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
557                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
558
559                                                 log_status_skip(LOG_DEBUG_FILTERING,
560                                                                 _("rule is account-based [id=%d, name='%s'], "
561                                                                 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
562                                                                 filtering->account_id, account->account_name,
563                                                                 ac_prefs->account_id, ac_prefs->account_name);
564                                         }
565                                 }
566                         }
567                 }
568         } else {
569                 switch (prefs_common.apply_per_account_filtering_rules) {
570                 case FILTERING_ACCOUNT_RULES_FORCE:
571                         /* apply filtering rules regardless to the account info */
572                         matches = TRUE;
573
574                         /* debug output */
575                         if (debug_filtering_session) {
576                                 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
577                                         if (filtering->account_id == 0) {
578                                                 log_status_ok(LOG_DEBUG_FILTERING,
579                                                                 _("rule is not account-based, "
580                                                                 "all rules are applied on user request anyway\n"));
581                                         } else {
582                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
583
584                                                 log_status_ok(LOG_DEBUG_FILTERING,
585                                                                 _("rule is account-based [id=%d, name='%s'], "
586                                                                 "but all rules are applied on user request\n"),
587                                                                 filtering->account_id, account->account_name);
588                                         }
589                                 }
590                         }
591                         break;
592                 case FILTERING_ACCOUNT_RULES_SKIP:
593                         /* don't apply filtering rules that belong to an account */
594                         matches = (filtering->account_id == 0);
595
596                         /* debug output */
597                         if (debug_filtering_session) {
598                                 if (!matches) {
599                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
600                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
601
602                                                 log_status_skip(LOG_DEBUG_FILTERING,
603                                                                 _("rule is account-based [id=%d, name='%s'], "
604                                                                 "skipped on user request\n"),
605                                                                 filtering->account_id, account->account_name);
606                                         } else {
607                                                 log_status_skip(LOG_DEBUG_FILTERING,
608                                                                 _("rule is account-based, "
609                                                                 "skipped on user request\n"));
610                                         }
611                                 } else {
612                                         if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
613                                                 log_status_ok(LOG_DEBUG_FILTERING,
614                                                                 _("rule is not account-based\n"));
615                                         }
616                                 }
617                         }
618                         break;
619                 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
620                         matches = ((filtering->account_id == 0)
621                                         || (filtering->account_id == cur_account->account_id));
622
623                         /* debug output */
624                         if (debug_filtering_session) {
625                                 if (!matches) {
626                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
627                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
628
629                                                 log_status_skip(LOG_DEBUG_FILTERING,
630                                                                 _("rule is account-based [id=%d, name='%s'], "
631                                                                 "not matching current account [id=%d, name='%s']\n"),
632                                                                 filtering->account_id, account->account_name,
633                                                                 cur_account->account_id, cur_account->account_name);
634                                         } else {
635                                                 log_status_skip(LOG_DEBUG_FILTERING,
636                                                                 _("rule is account-based, "
637                                                                 "not matching current account\n"));
638                                         }
639                                 } else {
640                                         if (matches && 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\n"));
644                                                 } else {
645                                                         PrefsAccount *account = account_find_from_id(filtering->account_id);
646
647                                                         log_status_ok(LOG_DEBUG_FILTERING,
648                                                                         _("rule is account-based [id=%d, name='%s'], "
649                                                                         "current account [id=%d, name='%s']\n"),
650                                                                         account->account_id, account->account_name,
651                                                                         cur_account->account_id, cur_account->account_name);
652                                                 }
653                                         }
654                                 }
655                         }
656                         break;
657                 }
658         }
659
660         return matches && matcherlist_match(filtering->matchers, info);
661 }
662
663 /*!
664  *\brief        Apply a rule on message.
665  *
666  *\param        filtering List of filtering rules.
667  *\param        info Message to apply rules on.
668  *\param        final Variable returning TRUE or FALSE if one of the
669  *              encountered actions was final. 
670  *              See also \ref filtering_is_final_action.
671  *
672  *\return       gboolean TRUE to continue applying rules.
673  */
674 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
675     gboolean * final)
676 {
677         gboolean result = TRUE;
678         gchar    buf[256];
679         GSList * tmp;
680         
681         * final = FALSE;
682         for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
683                 FilteringAction * action;
684
685                 action = tmp->data;
686                                 filteringaction_to_string(buf, sizeof buf, action);
687                                 if (debug_filtering_session)
688                                         log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
689
690                 if (FALSE == (result = filteringaction_apply(action, info))) {
691                                         if (debug_filtering_session) {
692                                                 if (action->type != MATCHACTION_STOP)
693                                                         log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
694                                                 log_print(LOG_DEBUG_FILTERING,
695                                                                 _("no further processing after action [ %s ]\n"), buf);
696                                         } else
697                                                 g_warning("No further processing after rule %s\n", buf);
698                 }
699                 
700                 if (filtering_is_final_action(action)) {
701                         * final = TRUE;
702                         break;
703                 }
704         }
705         return result;
706 }
707
708 /*!
709  *\brief        Check if an action is "final", i.e. should break further
710  *              processing.
711  *
712  *\param        filtering_action Action to check.
713  *
714  *\return       gboolean TRUE if \a filtering_action is final.  
715  */
716 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
717 {
718         switch(filtering_action->type) {
719         case MATCHACTION_MOVE:
720         case MATCHACTION_DELETE:
721         case MATCHACTION_STOP:
722         case MATCHACTION_MARK_AS_SPAM:
723                 return TRUE; /* MsgInfo invalid for message */
724         default:
725                 return FALSE;
726         }
727 }
728
729 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
730 {
731         GSList  *l;
732         gboolean final;
733         gboolean apply_next;
734         
735         g_return_val_if_fail(info != NULL, TRUE);
736         
737         for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
738                 FilteringProp * filtering = (FilteringProp *) l->data;
739
740                 if (filtering->enabled) {
741                         if (debug_filtering_session) {
742                                 gchar *buf = filteringprop_to_string(filtering);
743                                 if (filtering->name && *filtering->name != '\0') {
744                                         log_print(LOG_DEBUG_FILTERING,
745                                                 _("processing rule '%s' [ %s ]\n"),
746                                                 filtering->name, buf);
747                                 } else {
748                                         log_print(LOG_DEBUG_FILTERING,
749                                                 _("processing rule <unnamed> [ %s ]\n"),
750                                                 buf);
751                                 }
752                         }
753
754                         if (filtering_match_condition(filtering, info, ac_prefs)) {
755                                 apply_next = filtering_apply_rule(filtering, info, &final);
756                                 if (final)
757                                         break;
758                         }
759
760                 } else {
761                         if (debug_filtering_session) {
762                                 gchar *buf = filteringprop_to_string(filtering);
763                                 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
764                                         if (filtering->name && *filtering->name != '\0') {
765                                                 log_status_skip(LOG_DEBUG_FILTERING,
766                                                                 _("disabled rule '%s' [ %s ]\n"),
767                                                                 filtering->name, buf);
768                                         } else {
769                                                 log_status_skip(LOG_DEBUG_FILTERING,
770                                                                 _("disabled rule <unnamed> [ %s ]\n"),
771                                                                 buf);
772                                         }
773                                 }
774                                 g_free(buf);
775                         }
776                 }
777         }
778
779         /* put in inbox if a final rule could not be applied, or
780          * the last rule was not a final one. */
781         if ((final && !apply_next) || !final) {
782                 return FALSE;
783         }
784
785         return TRUE;
786 }
787
788 /*!
789  *\brief        Filter a message against a list of rules.
790  *
791  *\param        flist List of filter rules.
792  *\param        info Message.
793  *
794  *\return       gboolean TRUE if filter rules handled the message.
795  *
796  *\note         Returning FALSE means the message was not handled,
797  *              and that the calling code should do the default
798  *              processing. E.g. \ref inc.c::inc_start moves the 
799  *              message to the inbox.   
800  */
801 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
802                                                                    FilteringInvocationType context, gchar *extra_info)
803 {
804         if (prefs_common.enable_filtering_debug) {
805                 gchar *tmp = _("undetermined");
806
807                 switch (context) {
808                 case FILTERING_INCORPORATION:
809                         tmp = _("incorporation");
810                         debug_filtering_session = prefs_common.enable_filtering_debug_inc;
811                         break;
812                 case FILTERING_MANUALLY:
813                         tmp = _("manually");
814                         debug_filtering_session = prefs_common.enable_filtering_debug_manual;
815                         break;
816                 case FILTERING_FOLDER_PROCESSING:
817                         tmp = _("folder processing");
818                         debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
819                         break;
820                 case FILTERING_PRE_PROCESSING:
821                         tmp = _("pre-processing");
822                         debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
823                         break;
824                 case FILTERING_POST_PROCESSING:
825                         tmp = _("post-processing");
826                         debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
827                         break;
828                 default:
829                         debug_filtering_session = FALSE;
830                         break;
831                 }
832                 if (debug_filtering_session) {
833                         gchar *file = procmsg_get_message_file_path(info);
834                         gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
835
836                         /* show context info and essential info about the message */
837                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
838                                 log_print(LOG_DEBUG_FILTERING,
839                                                 _("filtering message (%s%s%s)\n"
840                                                 "%smessage file: %s\n%sDate: %s\n%sFrom: %s\n%sTo: %s\n%sSubject: %s\n"),
841                                                 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
842                                                 spc, file, spc, info->date, spc, info->from,
843                                                 spc, info->to, spc, info->subject);
844                         } else {
845                                 log_print(LOG_DEBUG_FILTERING,
846                                                 _("filtering message (%s%s%s)\n"
847                                                 "%smessage file: %s\n"),
848                                                 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
849                                                 spc, file);
850                         }
851                         g_free(file);
852                         g_free(spc);
853                 }
854         } else
855                 debug_filtering_session = FALSE;
856         return filter_msginfo(flist, info, ac_prefs);
857 }
858
859 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
860 {
861         const gchar *command_str;
862         gchar * quoted_dest;
863         gchar * quoted_header;
864         
865         command_str = get_matchparser_tab_str(action->type);
866
867         if (command_str == NULL)
868                 return NULL;
869
870         switch(action->type) {
871         case MATCHACTION_MOVE:
872         case MATCHACTION_COPY:
873         case MATCHACTION_EXECUTE:
874                 quoted_dest = matcher_quote_str(action->destination);
875                 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
876                 g_free(quoted_dest);
877                 return dest;
878
879         case MATCHACTION_DELETE:
880         case MATCHACTION_MARK:
881         case MATCHACTION_UNMARK:
882         case MATCHACTION_LOCK:
883         case MATCHACTION_UNLOCK:
884         case MATCHACTION_MARK_AS_READ:
885         case MATCHACTION_MARK_AS_UNREAD:
886         case MATCHACTION_MARK_AS_SPAM:
887         case MATCHACTION_MARK_AS_HAM:
888         case MATCHACTION_STOP:
889         case MATCHACTION_HIDE:
890         case MATCHACTION_IGNORE:
891                 g_snprintf(dest, destlen, "%s", command_str);
892                 return dest;
893
894         case MATCHACTION_REDIRECT:
895         case MATCHACTION_FORWARD:
896         case MATCHACTION_FORWARD_AS_ATTACHMENT:
897                 quoted_dest = matcher_quote_str(action->destination);
898                 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
899                 g_free(quoted_dest);
900                 return dest; 
901
902         case MATCHACTION_COLOR:
903                 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
904                 return dest;  
905
906         case MATCHACTION_CHANGE_SCORE:
907         case MATCHACTION_SET_SCORE:
908                 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
909                 return dest;  
910
911         case MATCHACTION_ADD_TO_ADDRESSBOOK:
912                 quoted_header = matcher_quote_str(action->header);
913                 quoted_dest = matcher_quote_str(action->destination);
914                 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
915                 g_free(quoted_dest);
916                 g_free(quoted_header);
917                 return dest;
918
919         default:
920                 return NULL;
921         }
922 }
923
924 gchar * filteringaction_list_to_string(GSList * action_list)
925 {
926         gchar *action_list_str;
927         gchar  buf[256];
928         GSList * tmp;
929         gchar *list_str;
930
931         action_list_str = NULL;
932         for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
933                 gchar *action_str;
934                 FilteringAction * action;
935                 
936                 action = tmp->data;
937                 
938                 action_str = filteringaction_to_string(buf,
939                     sizeof buf, action);
940                 
941                 if (action_list_str != NULL) {
942                         list_str = g_strconcat(action_list_str, " ", action_str, NULL);
943                         g_free(action_list_str);
944                 }
945                 else {
946                         list_str = g_strdup(action_str);
947                 }
948                 action_list_str = list_str;
949         }
950
951         return action_list_str;
952 }
953
954 gchar * filteringprop_to_string(FilteringProp * prop)
955 {
956         gchar *list_str;
957         gchar *action_list_str;
958         gchar *filtering_str;
959
960         action_list_str = filteringaction_list_to_string(prop->action_list);
961
962         if (action_list_str == NULL)
963                 return NULL;
964
965         list_str = matcherlist_to_string(prop->matchers);
966
967         if (list_str == NULL) {
968                 g_free(action_list_str);
969                 return NULL;
970         }
971
972         filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
973         g_free(action_list_str);
974         g_free(list_str);
975
976         return filtering_str;
977 }
978
979 void prefs_filtering_free(GSList * prefs_filtering)
980 {
981         while (prefs_filtering != NULL) {
982                 FilteringProp * filtering = (FilteringProp *)
983                         prefs_filtering->data;
984                 filteringprop_free(filtering);
985                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
986         }
987 }
988
989 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
990 {
991         FolderItem *item = node->data;
992
993         g_return_val_if_fail(item, FALSE);
994         g_return_val_if_fail(item->prefs, FALSE);
995
996         prefs_filtering_free(item->prefs->processing);
997         item->prefs->processing = NULL;
998
999         return FALSE;
1000 }
1001
1002 void prefs_filtering_clear(void)
1003 {
1004         GList * cur;
1005
1006         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1007                 Folder *folder;
1008
1009                 folder = (Folder *) cur->data;
1010                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1011                                 prefs_filtering_free_func, NULL);
1012         }
1013
1014         prefs_filtering_free(filtering_rules);
1015         filtering_rules = NULL;
1016         prefs_filtering_free(pre_global_processing);
1017         pre_global_processing = NULL;
1018         prefs_filtering_free(post_global_processing);
1019         post_global_processing = NULL;
1020 }
1021
1022 void prefs_filtering_clear_folder(Folder *folder)
1023 {
1024         g_return_if_fail(folder);
1025         g_return_if_fail(folder->node);
1026
1027         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1028                         prefs_filtering_free_func, NULL);
1029         /* FIXME: Note folder settings were changed, where the updates? */
1030 }
1031
1032 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1033 /* return TRUE if there's at least one per-account filtering rule */
1034 {
1035         GSList *l;
1036
1037         for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1038                 FilteringProp * filtering = (FilteringProp *) l->data;
1039
1040                 if (filtering->enabled && (filtering->account_id != 0)) {
1041                         return TRUE;
1042                 }               
1043         }
1044
1045         return FALSE;
1046 }