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