2007-06-29 [wwp] 2.9.2cvs76
[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                         if (prefs_common.real_time_sync)
243                                 folder_item_synchronise(last_item);
244                         g_slist_free(batch);
245                         batch = NULL;
246                 }
247                 last_item = NULL;
248                 is_copy = FALSE;
249                 is_move = FALSE;
250         }
251         /* we don't reference the msginfos, because caller will do */
252         g_slist_free(messages);
253 }
254
255 /*
256   fitleringaction_apply
257   runs the action on one MsgInfo
258   return value : return TRUE if the action could be applied
259 */
260
261 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
262 {
263         FolderItem * dest_folder;
264         gint val;
265         Compose * compose;
266         PrefsAccount * account;
267         gchar * cmd;
268
269         switch(action->type) {
270         case MATCHACTION_MOVE:
271                 dest_folder =
272                         folder_find_item_from_identifier(action->destination);
273                 if (!dest_folder) {
274                         debug_print("*** folder not found '%s'\n",
275                                 action->destination ?action->destination :"");
276                         return FALSE;
277                 }
278                 
279                 /* check if mail is set to copy already, 
280                  * in which case we have to do it */
281                 if (info->is_copy && info->to_filter_folder) {
282                         debug_print("should cp and mv !\n");
283                         folder_item_copy_msg(info->to_filter_folder, info);
284                         info->is_copy = FALSE;
285                 }
286                 /* mark message to be moved */          
287                 info->is_move = TRUE;
288                 info->to_filter_folder = dest_folder;
289                 return TRUE;
290
291         case MATCHACTION_COPY:
292                 dest_folder =
293                         folder_find_item_from_identifier(action->destination);
294
295                 if (!dest_folder) {
296                         debug_print("*** folder not found '%s'\n",
297                                 action->destination ?action->destination :"");
298                         return FALSE;
299                 }
300
301                 /* check if mail is set to copy already, 
302                  * in which case we have to do it */
303                 if (info->is_copy && info->to_filter_folder) {
304                         debug_print("should cp and mv !\n");
305                         folder_item_copy_msg(info->to_filter_folder, info);
306                         info->is_copy = FALSE;
307                 }
308                 /* mark message to be copied */         
309                 info->is_copy = TRUE;
310                 info->to_filter_folder = dest_folder;
311                 return TRUE;
312
313         case MATCHACTION_DELETE:
314                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
315                         return FALSE;
316                 return TRUE;
317
318         case MATCHACTION_MARK:
319                 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
320                 return TRUE;
321
322         case MATCHACTION_UNMARK:
323                 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
324                 return TRUE;
325
326         case MATCHACTION_LOCK:
327                 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
328                 return TRUE;
329
330         case MATCHACTION_UNLOCK:
331                 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);       
332                 return TRUE;
333                 
334         case MATCHACTION_MARK_AS_READ:
335                 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
336                 return TRUE;
337
338         case MATCHACTION_MARK_AS_UNREAD:
339                 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
340                 return TRUE;
341         
342         case MATCHACTION_MARK_AS_SPAM:
343                 procmsg_spam_learner_learn(info, NULL, TRUE);
344                 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
345                 if (procmsg_spam_get_folder(info)) {
346                         info->is_move = TRUE;
347                         info->to_filter_folder = procmsg_spam_get_folder(info);
348                 }
349                 return TRUE;
350
351         case MATCHACTION_MARK_AS_HAM:
352                 procmsg_spam_learner_learn(info, NULL, FALSE);
353                 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
354                 return TRUE;
355         
356         case MATCHACTION_COLOR:
357                 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0); 
358                 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
359                 return TRUE;
360
361         case MATCHACTION_FORWARD:
362         case MATCHACTION_FORWARD_AS_ATTACHMENT:
363                 account = account_find_from_id(action->account_id);
364                 compose = compose_forward(account, info,
365                         action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
366                         NULL, TRUE, TRUE);
367                 compose_entry_append(compose, action->destination,
368                                      compose->account->protocol == A_NNTP
369                                             ? COMPOSE_NEWSGROUPS
370                                             : COMPOSE_TO);
371
372                 val = compose_send(compose);
373
374                 return val == 0 ? TRUE : FALSE;
375
376         case MATCHACTION_REDIRECT:
377                 account = account_find_from_id(action->account_id);
378                 compose = compose_redirect(account, info, TRUE);
379                 if (compose->account->protocol == A_NNTP)
380                         break;
381                 else
382                         compose_entry_append(compose, action->destination,
383                                              COMPOSE_TO);
384
385                 val = compose_send(compose);
386                 
387                 return val == 0 ? TRUE : FALSE;
388
389         case MATCHACTION_EXECUTE:
390                 cmd = matching_build_command(action->destination, info);
391                 if (cmd == NULL)
392                         return FALSE;
393                 else {
394                         system(cmd);
395                         g_free(cmd);
396                 }
397                 return TRUE;
398
399         case MATCHACTION_SET_SCORE:
400                 info->score = action->score;
401                 return TRUE;
402
403         case MATCHACTION_CHANGE_SCORE:
404                 info->score += action->score;
405                 return TRUE;
406
407         case MATCHACTION_STOP:
408                 return FALSE;
409
410         case MATCHACTION_HIDE:
411                 info->hidden = TRUE;
412                 return TRUE;
413
414         case MATCHACTION_IGNORE:
415                 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
416                 return TRUE;
417
418         case MATCHACTION_ADD_TO_ADDRESSBOOK:
419                 {
420                         AddressDataSource *book = NULL;
421                         AddressBookFile *abf = NULL;
422                         ItemFolder *folder = NULL;
423                         gchar buf[BUFFSIZE];
424                         Header *header;
425                         gint errors = 0;
426
427                         if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
428                                 g_warning("addressbook folder not found '%s'\n", action->destination);
429                                 return FALSE;
430                         }
431                         if (!book) {
432                                 g_warning("addressbook_peek_folder_exists returned NULL book\n");
433                                 return FALSE;
434                         }
435
436                         abf = book->rawDataSource;
437
438                         /* get the header */
439                         procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
440                         header = procheader_parse_header(buf);
441
442                         /* add all addresses that are not already in */
443                         if (header && *header->body && (*header->body != '\0')) {
444                                 GSList *address_list = NULL;
445                                 GSList *walk = NULL;
446                                 gchar *path = NULL;
447
448                                 if (action->destination == NULL ||
449                                                 strcasecmp(action->destination, _("Any")) == 0 ||
450                                                 *(action->destination) == '\0')
451                                         path = NULL;
452                                 else
453                                         path = action->destination;
454                                 start_address_completion(path);
455
456                                 address_list = address_list_append(address_list, header->body);
457                                 for (walk = address_list; walk != NULL; walk = walk->next) {
458                                         gchar *stripped_addr = g_strdup(walk->data);
459                                         extract_address(stripped_addr);
460
461                                         if (complete_matches_found(walk->data) == 0) {
462                                                 debug_print("adding address '%s' to addressbook '%s'\n",
463                                                                 stripped_addr, action->destination);
464                                                 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
465                                                         g_warning("contact could not been added\n");
466                                                         errors++;
467                                                 }
468                                         } else {
469                                                 debug_print("address '%s' already found in addressbook '%s', skipping\n",
470                                                                 stripped_addr, action->destination);
471                                         }
472                                         g_free(stripped_addr);
473                                 }
474
475                                 g_slist_free(address_list);
476                                 end_address_completion();
477                         } else {
478                                 g_warning("header '%s' not set or empty\n", action->header);
479                         }
480                         return (errors == 0);
481                 }
482
483         default:
484                 break;
485         }
486         return FALSE;
487 }
488
489 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
490 {
491         GSList *p;
492         g_return_val_if_fail(action_list, FALSE);
493         g_return_val_if_fail(info, FALSE);
494         for (p = action_list; p && p->data; p = g_slist_next(p)) {
495                 FilteringAction *a = (FilteringAction *) p->data;
496                 if (filteringaction_apply(a, info)) {
497                         if (filtering_is_final_action(a))
498                                 break;
499                 } else
500                         return FALSE;
501                 
502         }
503         return TRUE;
504 }
505
506 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
507                                                         PrefsAccount *ac_prefs)
508
509 /* this function returns true if a filtering rule applies regarding to its account
510    data and if it does, if the conditions list match.
511
512    per-account data of a filtering rule is either matched against current account
513    when filtering is done manually, or against the account currently used for
514    retrieving messages when it's an manual or automatic fetching of messages.
515    per-account data match doesn't apply to pre-/post-/folder-processing rules.
516
517    when filtering messages manually:
518     - either the filtering rule is not account-based and it will be processed
519         - or it's per-account and we check if we HAVE TO match it against the current
520           account (according to user-land settings, per-account rules might have to
521           be skipped, or only the rules that match the current account have to be
522           applied, or all rules will have to be applied regardless to if they are
523           account-based or not)
524
525    notes about debugging output in that function:
526    when not matching, log_status_skip() is used, otherwise log_status_ok() is used
527    no debug output is done when filtering_debug_level is low
528 */
529 {
530         gboolean matches = FALSE;
531
532         if (ac_prefs != NULL) {
533                 matches = ((filtering->account_id == 0)
534                                         || (filtering->account_id == ac_prefs->account_id));
535
536                 /* debug output */
537                 if (debug_filtering_session) {
538                         if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
539                                 if (filtering->account_id == 0) {
540                                         log_status_ok(LOG_DEBUG_FILTERING,
541                                                         _("rule is not account-based\n"));
542                                 } else {
543                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
544                                                 log_status_ok(LOG_DEBUG_FILTERING,
545                                                                 _("rule is account-based [id=%d, name='%s'], "
546                                                                 "matching the account currently used to retrieve messages\n"),
547                                                                 ac_prefs->account_id, ac_prefs->account_name);
548                                         }
549                                 }
550                         }
551                         else
552                         if (!matches) {
553                                 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
554                                         log_status_skip(LOG_DEBUG_FILTERING,
555                                                         _("rule is account-based, "
556                                                         "not matching the account currently used to retrieve messages\n"));
557                                 } else {
558                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
559                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
560
561                                                 log_status_skip(LOG_DEBUG_FILTERING,
562                                                                 _("rule is account-based [id=%d, name='%s'], "
563                                                                 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
564                                                                 filtering->account_id, account->account_name,
565                                                                 ac_prefs->account_id, ac_prefs->account_name);
566                                         }
567                                 }
568                         }
569                 }
570         } else {
571                 switch (prefs_common.apply_per_account_filtering_rules) {
572                 case FILTERING_ACCOUNT_RULES_FORCE:
573                         /* apply filtering rules regardless to the account info */
574                         matches = TRUE;
575
576                         /* debug output */
577                         if (debug_filtering_session) {
578                                 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
579                                         if (filtering->account_id == 0) {
580                                                 log_status_ok(LOG_DEBUG_FILTERING,
581                                                                 _("rule is not account-based, "
582                                                                 "all rules are applied on user request anyway\n"));
583                                         } else {
584                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
585
586                                                 log_status_ok(LOG_DEBUG_FILTERING,
587                                                                 _("rule is account-based [id=%d, name='%s'], "
588                                                                 "but all rules are applied on user request\n"),
589                                                                 filtering->account_id, account->account_name);
590                                         }
591                                 }
592                         }
593                         break;
594                 case FILTERING_ACCOUNT_RULES_SKIP:
595                         /* don't apply filtering rules that belong to an account */
596                         matches = (filtering->account_id == 0);
597
598                         /* debug output */
599                         if (debug_filtering_session) {
600                                 if (!matches) {
601                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
602                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
603
604                                                 log_status_skip(LOG_DEBUG_FILTERING,
605                                                                 _("rule is account-based [id=%d, name='%s'], "
606                                                                 "skipped on user request\n"),
607                                                                 filtering->account_id, account->account_name);
608                                         } else {
609                                                 log_status_skip(LOG_DEBUG_FILTERING,
610                                                                 _("rule is account-based, "
611                                                                 "skipped on user request\n"));
612                                         }
613                                 } else {
614                                         if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
615                                                 log_status_ok(LOG_DEBUG_FILTERING,
616                                                                 _("rule is not account-based\n"));
617                                         }
618                                 }
619                         }
620                         break;
621                 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
622                         matches = ((filtering->account_id == 0)
623                                         || (filtering->account_id == cur_account->account_id));
624
625                         /* debug output */
626                         if (debug_filtering_session) {
627                                 if (!matches) {
628                                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
629                                                 PrefsAccount *account = account_find_from_id(filtering->account_id);
630
631                                                 log_status_skip(LOG_DEBUG_FILTERING,
632                                                                 _("rule is account-based [id=%d, name='%s'], "
633                                                                 "not matching current account [id=%d, name='%s']\n"),
634                                                                 filtering->account_id, account->account_name,
635                                                                 cur_account->account_id, cur_account->account_name);
636                                         } else {
637                                                 log_status_skip(LOG_DEBUG_FILTERING,
638                                                                 _("rule is account-based, "
639                                                                 "not matching current account\n"));
640                                         }
641                                 } else {
642                                         if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
643                                                 if (filtering->account_id == 0) {
644                                                         log_status_ok(LOG_DEBUG_FILTERING,
645                                                                         _("rule is not account-based\n"));
646                                                 } else {
647                                                         PrefsAccount *account = account_find_from_id(filtering->account_id);
648
649                                                         log_status_ok(LOG_DEBUG_FILTERING,
650                                                                         _("rule is account-based [id=%d, name='%s'], "
651                                                                         "current account [id=%d, name='%s']\n"),
652                                                                         account->account_id, account->account_name,
653                                                                         cur_account->account_id, cur_account->account_name);
654                                                 }
655                                         }
656                                 }
657                         }
658                         break;
659                 }
660         }
661
662         return matches && matcherlist_match(filtering->matchers, info);
663 }
664
665 /*!
666  *\brief        Apply a rule on message.
667  *
668  *\param        filtering List of filtering rules.
669  *\param        info Message to apply rules on.
670  *\param        final Variable returning TRUE or FALSE if one of the
671  *              encountered actions was final. 
672  *              See also \ref filtering_is_final_action.
673  *
674  *\return       gboolean TRUE to continue applying rules.
675  */
676 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
677     gboolean * final)
678 {
679         gboolean result = TRUE;
680         gchar    buf[256];
681         GSList * tmp;
682         
683         * final = FALSE;
684         for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
685                 FilteringAction * action;
686
687                 action = tmp->data;
688                                 filteringaction_to_string(buf, sizeof buf, action);
689                                 if (debug_filtering_session)
690                                         log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
691
692                 if (FALSE == (result = filteringaction_apply(action, info))) {
693                                         if (debug_filtering_session) {
694                                                 if (action->type != MATCHACTION_STOP)
695                                                         log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
696                                                 log_print(LOG_DEBUG_FILTERING,
697                                                                 _("no further processing after action [ %s ]\n"), buf);
698                                         } else
699                                                 g_warning("No further processing after rule %s\n", buf);
700                 }
701                 
702                 if (filtering_is_final_action(action)) {
703                         * final = TRUE;
704                         break;
705                 }
706         }
707         return result;
708 }
709
710 /*!
711  *\brief        Check if an action is "final", i.e. should break further
712  *              processing.
713  *
714  *\param        filtering_action Action to check.
715  *
716  *\return       gboolean TRUE if \a filtering_action is final.  
717  */
718 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
719 {
720         switch(filtering_action->type) {
721         case MATCHACTION_MOVE:
722         case MATCHACTION_DELETE:
723         case MATCHACTION_STOP:
724         case MATCHACTION_MARK_AS_SPAM:
725                 return TRUE; /* MsgInfo invalid for message */
726         default:
727                 return FALSE;
728         }
729 }
730
731 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
732 {
733         GSList  *l;
734         gboolean final;
735         gboolean apply_next;
736         
737         g_return_val_if_fail(info != NULL, TRUE);
738         
739         for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
740                 FilteringProp * filtering = (FilteringProp *) l->data;
741
742                 if (filtering->enabled) {
743                         if (debug_filtering_session) {
744                                 gchar *buf = filteringprop_to_string(filtering);
745                                 if (filtering->name && *filtering->name != '\0') {
746                                         log_print(LOG_DEBUG_FILTERING,
747                                                 _("processing rule '%s' [ %s ]\n"),
748                                                 filtering->name, buf);
749                                 } else {
750                                         log_print(LOG_DEBUG_FILTERING,
751                                                 _("processing rule <unnamed> [ %s ]\n"),
752                                                 buf);
753                                 }
754                         }
755
756                         if (filtering_match_condition(filtering, info, ac_prefs)) {
757                                 apply_next = filtering_apply_rule(filtering, info, &final);
758                                 if (final)
759                                         break;
760                         }
761
762                 } else {
763                         if (debug_filtering_session) {
764                                 gchar *buf = filteringprop_to_string(filtering);
765                                 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
766                                         if (filtering->name && *filtering->name != '\0') {
767                                                 log_status_skip(LOG_DEBUG_FILTERING,
768                                                                 _("disabled rule '%s' [ %s ]\n"),
769                                                                 filtering->name, buf);
770                                         } else {
771                                                 log_status_skip(LOG_DEBUG_FILTERING,
772                                                                 _("disabled rule <unnamed> [ %s ]\n"),
773                                                                 buf);
774                                         }
775                                 }
776                                 g_free(buf);
777                         }
778                 }
779         }
780
781         /* put in inbox if a final rule could not be applied, or
782          * the last rule was not a final one. */
783         if ((final && !apply_next) || !final) {
784                 return FALSE;
785         }
786
787         return TRUE;
788 }
789
790 /*!
791  *\brief        Filter a message against a list of rules.
792  *
793  *\param        flist List of filter rules.
794  *\param        info Message.
795  *
796  *\return       gboolean TRUE if filter rules handled the message.
797  *
798  *\note         Returning FALSE means the message was not handled,
799  *              and that the calling code should do the default
800  *              processing. E.g. \ref inc.c::inc_start moves the 
801  *              message to the inbox.   
802  */
803 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
804                                                                    FilteringInvocationType context, gchar *extra_info)
805 {
806         if (prefs_common.enable_filtering_debug) {
807                 gchar *tmp = _("undetermined");
808
809                 switch (context) {
810                 case FILTERING_INCORPORATION:
811                         tmp = _("incorporation");
812                         debug_filtering_session = prefs_common.enable_filtering_debug_inc;
813                         break;
814                 case FILTERING_MANUALLY:
815                         tmp = _("manually");
816                         debug_filtering_session = prefs_common.enable_filtering_debug_manual;
817                         break;
818                 case FILTERING_FOLDER_PROCESSING:
819                         tmp = _("folder processing");
820                         debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
821                         break;
822                 case FILTERING_PRE_PROCESSING:
823                         tmp = _("pre-processing");
824                         debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
825                         break;
826                 case FILTERING_POST_PROCESSING:
827                         tmp = _("post-processing");
828                         debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
829                         break;
830                 default:
831                         debug_filtering_session = FALSE;
832                         break;
833                 }
834                 if (debug_filtering_session) {
835                         gchar *file = procmsg_get_message_file_path(info);
836                         gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
837
838                         /* show context info and essential info about the message */
839                         if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
840                                 log_print(LOG_DEBUG_FILTERING,
841                                                 _("filtering message (%s%s%s)\n"
842                                                 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
843                                                 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
844                                                 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
845                                                 spc, prefs_common_translated_header_name("From:"), info->from,
846                                                 spc, prefs_common_translated_header_name("To:"), info->to,
847                                                 spc, prefs_common_translated_header_name("Subject:"), info->subject);
848                         } else {
849                                 log_print(LOG_DEBUG_FILTERING,
850                                                 _("filtering message (%s%s%s)\n"
851                                                 "%smessage file: %s\n"),
852                                                 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
853                                                 spc, file);
854                         }
855                         g_free(file);
856                         g_free(spc);
857                 }
858         } else
859                 debug_filtering_session = FALSE;
860         return filter_msginfo(flist, info, ac_prefs);
861 }
862
863 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
864 {
865         const gchar *command_str;
866         gchar * quoted_dest;
867         gchar * quoted_header;
868         
869         command_str = get_matchparser_tab_str(action->type);
870
871         if (command_str == NULL)
872                 return NULL;
873
874         switch(action->type) {
875         case MATCHACTION_MOVE:
876         case MATCHACTION_COPY:
877         case MATCHACTION_EXECUTE:
878                 quoted_dest = matcher_quote_str(action->destination);
879                 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
880                 g_free(quoted_dest);
881                 return dest;
882
883         case MATCHACTION_DELETE:
884         case MATCHACTION_MARK:
885         case MATCHACTION_UNMARK:
886         case MATCHACTION_LOCK:
887         case MATCHACTION_UNLOCK:
888         case MATCHACTION_MARK_AS_READ:
889         case MATCHACTION_MARK_AS_UNREAD:
890         case MATCHACTION_MARK_AS_SPAM:
891         case MATCHACTION_MARK_AS_HAM:
892         case MATCHACTION_STOP:
893         case MATCHACTION_HIDE:
894         case MATCHACTION_IGNORE:
895                 g_snprintf(dest, destlen, "%s", command_str);
896                 return dest;
897
898         case MATCHACTION_REDIRECT:
899         case MATCHACTION_FORWARD:
900         case MATCHACTION_FORWARD_AS_ATTACHMENT:
901                 quoted_dest = matcher_quote_str(action->destination);
902                 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
903                 g_free(quoted_dest);
904                 return dest; 
905
906         case MATCHACTION_COLOR:
907                 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
908                 return dest;  
909
910         case MATCHACTION_CHANGE_SCORE:
911         case MATCHACTION_SET_SCORE:
912                 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
913                 return dest;  
914
915         case MATCHACTION_ADD_TO_ADDRESSBOOK:
916                 quoted_header = matcher_quote_str(action->header);
917                 quoted_dest = matcher_quote_str(action->destination);
918                 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
919                 g_free(quoted_dest);
920                 g_free(quoted_header);
921                 return dest;
922
923         default:
924                 return NULL;
925         }
926 }
927
928 gchar * filteringaction_list_to_string(GSList * action_list)
929 {
930         gchar *action_list_str;
931         gchar  buf[256];
932         GSList * tmp;
933         gchar *list_str;
934
935         action_list_str = NULL;
936         for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
937                 gchar *action_str;
938                 FilteringAction * action;
939                 
940                 action = tmp->data;
941                 
942                 action_str = filteringaction_to_string(buf,
943                     sizeof buf, action);
944                 
945                 if (action_list_str != NULL) {
946                         list_str = g_strconcat(action_list_str, " ", action_str, NULL);
947                         g_free(action_list_str);
948                 }
949                 else {
950                         list_str = g_strdup(action_str);
951                 }
952                 action_list_str = list_str;
953         }
954
955         return action_list_str;
956 }
957
958 gchar * filteringprop_to_string(FilteringProp * prop)
959 {
960         gchar *list_str;
961         gchar *action_list_str;
962         gchar *filtering_str;
963
964         action_list_str = filteringaction_list_to_string(prop->action_list);
965
966         if (action_list_str == NULL)
967                 return NULL;
968
969         list_str = matcherlist_to_string(prop->matchers);
970
971         if (list_str == NULL) {
972                 g_free(action_list_str);
973                 return NULL;
974         }
975
976         filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
977         g_free(action_list_str);
978         g_free(list_str);
979
980         return filtering_str;
981 }
982
983 void prefs_filtering_free(GSList * prefs_filtering)
984 {
985         while (prefs_filtering != NULL) {
986                 FilteringProp * filtering = (FilteringProp *)
987                         prefs_filtering->data;
988                 filteringprop_free(filtering);
989                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
990         }
991 }
992
993 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
994 {
995         FolderItem *item = node->data;
996
997         g_return_val_if_fail(item, FALSE);
998         g_return_val_if_fail(item->prefs, FALSE);
999
1000         prefs_filtering_free(item->prefs->processing);
1001         item->prefs->processing = NULL;
1002
1003         return FALSE;
1004 }
1005
1006 void prefs_filtering_clear(void)
1007 {
1008         GList * cur;
1009
1010         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1011                 Folder *folder;
1012
1013                 folder = (Folder *) cur->data;
1014                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1015                                 prefs_filtering_free_func, NULL);
1016         }
1017
1018         prefs_filtering_free(filtering_rules);
1019         filtering_rules = NULL;
1020         prefs_filtering_free(pre_global_processing);
1021         pre_global_processing = NULL;
1022         prefs_filtering_free(post_global_processing);
1023         post_global_processing = NULL;
1024 }
1025
1026 void prefs_filtering_clear_folder(Folder *folder)
1027 {
1028         g_return_if_fail(folder);
1029         g_return_if_fail(folder->node);
1030
1031         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1032                         prefs_filtering_free_func, NULL);
1033         /* FIXME: Note folder settings were changed, where the updates? */
1034 }
1035
1036 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1037 /* return TRUE if there's at least one per-account filtering rule */
1038 {
1039         GSList *l;
1040
1041         for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1042                 FilteringProp * filtering = (FilteringProp *) l->data;
1043
1044                 if (filtering->enabled && (filtering->account_id != 0)) {
1045                         return TRUE;
1046                 }               
1047         }
1048
1049         return FALSE;
1050 }