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