visual indicator locked ("keep"); not yet completed
[claws.git] / src / filtering.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto & The Sylpheed Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 /* 
21  * initital     Hoa             initial
22  * 07/16/01     Alfons          fix bugs
23  * 07/18/01     Alfons          rewrite things
24  */
25
26 /* 07/18/01 (alfons) 
27  * 
28  * filter_message() is no longer the entry point for filtering. it's
29  * replaced with filter_incoming_message() which does not trust
30  * on MsgInfo's from a folder. instead it directly works with
31  * the temporary file.  
32  * updating the mark file is a lot easier now, because we can do that
33  * right after a call to folder_item_add_msg(). 
34  */ 
35
36 #include "defs.h"
37 #include <ctype.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <errno.h>
41 #include <gtk/gtk.h>
42 #include <stdio.h>
43 #include "intl.h"
44 #include "utils.h"
45 #include "procheader.h"
46 #include "matcher.h"
47 #include "filtering.h"
48 #include "prefs.h"
49 #include "compose.h"
50
51 #define PREFSBUFSIZE            1024
52
53 GSList * global_processing = NULL;
54
55 FilteringAction * filteringaction_new(int type, int account_id,
56                                       gchar * destination,
57                                       gint labelcolor)
58 {
59         FilteringAction * action;
60
61         action = g_new0(FilteringAction, 1);
62
63         action->type = type;
64         action->account_id = account_id;
65         if (destination)
66                 action->destination = g_strdup(destination);
67         action->labelcolor = labelcolor;        
68         return action;
69 }
70
71 void filteringaction_free(FilteringAction * action)
72 {
73         if (action->destination)
74                 g_free(action->destination);
75         g_free(action);
76 }
77
78 /*
79 FilteringAction * filteringaction_parse(gchar ** str)
80 {
81         FilteringAction * action;
82         gchar * tmp;
83         gchar * destination = NULL;
84         gint account_id = 0;
85         gint key;
86         gint labelcolor = 0;
87
88         tmp = * str;
89
90         key = matcher_parse_keyword(&tmp);
91
92         switch (key) {
93         case MATCHACTION_MOVE:
94         case MATCHACTION_COPY:
95         case MATCHACTION_EXECUTE:
96                 destination = matcher_parse_str(&tmp);
97                 if (tmp == NULL) {
98                         * str = NULL;
99                         return NULL;
100                 }
101                 break;
102         case MATCHACTION_DELETE:
103                 break;
104         case MATCHACTION_MARK:
105                 break;
106         case MATCHACTION_MARK_AS_READ:
107                 break;
108         case MATCHACTION_UNMARK:
109                 break;
110         case MATCHACTION_MARK_AS_UNREAD:
111                 break;
112         case MATCHACTION_FORWARD:
113         case MATCHACTION_FORWARD_AS_ATTACHMENT:
114                 account_id = matcher_parse_number(&tmp);
115                 if (tmp == NULL) {
116                         * str = NULL;
117                         return NULL;
118                 }
119
120                 destination = matcher_parse_str(&tmp);
121                 if (tmp == NULL) {
122                         * str = NULL;
123                         return NULL;
124                 }
125
126                 break;
127         case MATCHACTION_COLOR:
128                 labelcolor = matcher_parse_number(&tmp);
129                 if (tmp == NULL) {
130                         *str = NULL;
131                         return NULL;
132                 }
133
134                 break;
135         default:
136                 * str = NULL;
137                 return NULL;
138         }
139
140         * str = tmp;
141
142         action = filteringaction_new(key, account_id, destination, labelcolor);
143
144         return action;
145 }
146
147 FilteringProp * filteringprop_parse(gchar ** str)
148 {
149         gchar * tmp;
150         gint key;
151         FilteringProp * filtering;
152         MatcherList * matchers;
153         FilteringAction * action;
154         
155         tmp = * str;
156
157         matchers = matcherlist_parse(&tmp);
158         if (tmp == NULL) {
159                 * str = NULL;
160                 return NULL;
161         }
162
163         if (tmp == NULL) {
164                 matcherlist_free(matchers);
165                 * str = NULL;
166                 return NULL;
167         }
168
169         action = filteringaction_parse(&tmp);
170         if (tmp == NULL) {
171                 matcherlist_free(matchers);
172                 * str = NULL;
173                 return NULL;
174         }
175
176         filtering = filteringprop_new(matchers, action);
177
178         * str = tmp;
179         return filtering;
180 }
181 */
182
183 FilteringProp * filteringprop_new(MatcherList * matchers,
184                                   FilteringAction * action)
185 {
186         FilteringProp * filtering;
187
188         filtering = g_new0(FilteringProp, 1);
189         filtering->matchers = matchers;
190         filtering->action = action;
191
192         return filtering;
193 }
194
195 void filteringprop_free(FilteringProp * prop)
196 {
197         matcherlist_free(prop->matchers);
198         filteringaction_free(prop->action);
199         g_free(prop);
200 }
201
202 /* filteringaction_update_mark() - updates a mark for a message. note that
203  * the message should not have been moved or copied. remember that the
204  * procmsg_open_mark_file(PATH, TRUE) actually _appends_ a new record.
205  */
206 static gboolean filteringaction_update_mark(MsgInfo * info)
207 {
208         gchar * dest_path;
209         FILE * fp;
210
211         if (info->folder->folder->type == F_MH) {
212                 dest_path = folder_item_get_path(info->folder);
213                 if (!is_dir_exist(dest_path))
214                         make_dir_hier(dest_path);
215                 
216                 if (dest_path == NULL) {
217                         g_warning(_("Can't open mark file.\n"));
218                         return FALSE;
219                 }
220                 
221                 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
222                     == NULL) {
223                         g_warning(_("Can't open mark file.\n"));
224                         return FALSE;
225                 }
226                 
227                 procmsg_write_flags(info, fp);
228                 fclose(fp);
229                 return TRUE;
230         }
231         return FALSE;
232 }
233
234 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
235 {
236         if (str) 
237                 return strlen(str);
238         else {
239                 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
240                 return 0;
241         }
242 }
243
244 #define STRLEN_WITH_CHECK(expr) \
245         strlen_with_check(#expr, __LINE__, expr)
246         
247 static gchar * filteringaction_execute_command(gchar * cmd, MsgInfo * info)
248 {
249         gchar * s = cmd;
250         gchar * filename = NULL;
251         gchar * processed_cmd;
252         gchar * p;
253         gint size;
254
255         matcher_unescape_str(cmd);
256
257         size = strlen(cmd) + 1;
258         while (*s != '\0') {
259                 if (*s == '%') {
260                         s++;
261                         switch (*s) {
262                         case '%':
263                                 size -= 1;
264                                 break;
265                         case 's': /* subject */
266                                 size += STRLEN_WITH_CHECK(info->subject) - 2;
267                                 break;
268                         case 'f': /* from */
269                                 size += STRLEN_WITH_CHECK(info->from) - 2;
270                                 break;
271                         case 't': /* to */
272                                 size += STRLEN_WITH_CHECK(info->to) - 2;
273                                 break;
274                         case 'c': /* cc */
275                                 size += STRLEN_WITH_CHECK(info->cc) - 2;
276                                 break;
277                         case 'd': /* date */
278                                 size += STRLEN_WITH_CHECK(info->date) - 2;
279                                 break;
280                         case 'i': /* message-id */
281                                 size += STRLEN_WITH_CHECK(info->msgid) - 2;
282                                 break;
283                         case 'n': /* newsgroups */
284                                 size += STRLEN_WITH_CHECK(info->newsgroups) - 2;
285                                 break;
286                         case 'r': /* references */
287                                 size += STRLEN_WITH_CHECK(info->references) - 2;
288                                 break;
289                         case 'F': /* file */
290                                 if (MSG_IS_FILTERING(info->flags))
291                                         filename = g_strdup((gchar *)info->folder);
292                                 else
293                                         filename = folder_item_fetch_msg(info->folder, info->msgnum);
294                                 
295                                 if (filename == NULL) {
296                                         g_warning(_("filename is not set"));
297                                         return NULL;
298                                 }
299                                 else
300                                         size += strlen(filename) - 2;
301                                 break;
302                         }
303                         s++;
304                 }
305                 else s++;
306         }
307
308
309         processed_cmd = g_new0(gchar, size);
310         s = cmd;
311         p = processed_cmd;
312
313         while (*s != '\0') {
314                 if (*s == '%') {
315                         s++;
316                         switch (*s) {
317                         case '%':
318                                 *p = '%';
319                                 p++;
320                                 break;
321                         case 's': /* subject */
322                                 if (info->subject != NULL)
323                                         strcpy(p, info->subject);
324                                 else
325                                         strcpy(p, "(none)");
326                                 p += strlen(p);
327                                 break;
328                         case 'f': /* from */
329                                 if (info->from != NULL)
330                                         strcpy(p, info->from);
331                                 else
332                                         strcpy(p, "(none)");
333                                 p += strlen(p);
334                                 break;
335                         case 't': /* to */
336                                 if (info->to != NULL)
337                                         strcpy(p, info->to);
338                                 else
339                                         strcpy(p, "(none)");
340                                 p += strlen(p);
341                                 break;
342                         case 'c': /* cc */
343                                 if (info->cc != NULL)
344                                         strcpy(p, info->cc);
345                                 else
346                                         strcpy(p, "(none)");
347                                 p += strlen(p);
348                                 break;
349                         case 'd': /* date */
350                                 if (info->date != NULL)
351                                         strcpy(p, info->date);
352                                 else
353                                         strcpy(p, "(none)");
354                                 p += strlen(p);
355                                 break;
356                         case 'i': /* message-id */
357                                 if (info->msgid != NULL)
358                                         strcpy(p, info->msgid);
359                                 else
360                                         strcpy(p, "(none)");
361                                 p += strlen(p);
362                                 break;
363                         case 'n': /* newsgroups */
364                                 if (info->newsgroups != NULL)
365                                         strcpy(p, info->newsgroups);
366                                 else
367                                         strcpy(p, "(none)");
368                                 p += strlen(p);
369                                 break;
370                         case 'r': /* references */
371                                 if (info->references != NULL)
372                                         strcpy(p, info->references);
373                                 else
374                                         strcpy(p, "(none)");
375                                 p += strlen(p);
376                                 break;
377                         case 'F': /* file */
378                                 strcpy(p, filename);
379                                 p += strlen(p);
380                                 break;
381                         default:
382                                 *p = '%';
383                                 p++;
384                                 *p = *s;
385                                 p++;
386                                 break;
387                         }
388                         s++;
389                 }
390                 else {
391                         *p = *s;
392                         p++;
393                         s++;
394                 }
395         }
396
397         g_free(filename);
398         return processed_cmd;
399 }
400
401 /*
402   fitleringaction_apply
403   runs the action on one MsgInfo
404   return value : return TRUE if the action could be applied
405 */
406
407 #define CHANGE_FLAGS(msginfo) \
408 { \
409 if (msginfo->folder->folder->change_flags != NULL) \
410 msginfo->folder->folder->change_flags(msginfo->folder->folder, \
411                                       msginfo->folder, \
412                                       msginfo); \
413 }
414
415 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info,
416                                       GHashTable *folder_table)
417 {
418         FolderItem * dest_folder;
419         gint val;
420         Compose * compose;
421         PrefsAccount * account;
422         gchar * cmd;
423
424         switch(action->type) {
425         case MATCHACTION_MOVE:
426                 dest_folder =
427                         folder_find_item_from_identifier(action->destination);
428                 if (!dest_folder)
429                         return FALSE;
430                 
431                 if (folder_item_move_msg(dest_folder, info) == -1) {
432                         return FALSE;
433                 }       
434
435                 /* WRONG: can not update the mark, because the message has 
436                  * been moved. info pertains to original location. 
437                  * folder_item_move_msg() already updated the mark for the
438                  * destination folder.
439                 info->flags = 0;
440                 filteringaction_update_mark(info);
441                  */
442                 if (folder_table) {
443                         val = GPOINTER_TO_INT(g_hash_table_lookup
444                                               (folder_table, dest_folder));
445                         if (val == 0) {
446                                 folder_item_scan(dest_folder);
447                                 g_hash_table_insert(folder_table, dest_folder,
448                                                     GINT_TO_POINTER(1));
449                         }
450                         val = GPOINTER_TO_INT(g_hash_table_lookup
451                                               (folder_table, info->folder));
452                         if (val == 0) {
453                                 folder_item_scan(info->folder);
454                                 g_hash_table_insert(folder_table, info->folder,
455                                                     GINT_TO_POINTER(1));
456                         }
457                 }
458                 return TRUE;
459
460         case MATCHACTION_COPY:
461                 dest_folder =
462                         folder_find_item_from_identifier(action->destination);
463
464                 if (!dest_folder)
465                         return FALSE;
466
467                 /* NOTE: the following call *will* update the mark file for
468                  * the destination folder. but the original message will
469                  * still be there in the inbox. */
470
471                 if (folder_item_copy_msg(dest_folder, info) == -1)
472                         return FALSE;
473
474                 if (folder_table) {
475                         val = GPOINTER_TO_INT(g_hash_table_lookup
476                                               (folder_table, dest_folder));
477                         if (val == 0) {
478                                 folder_item_scan(dest_folder);
479                                 g_hash_table_insert(folder_table, dest_folder,
480                                                     GINT_TO_POINTER(1));
481                         }
482                 }
483                 return TRUE;
484
485         case MATCHACTION_DELETE:
486                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
487                         return FALSE;
488
489                 /* WRONG: can not update the mark. this would actually add
490                  * a bogus record to the mark file for the message's original 
491                  * folder. 
492                 info->flags = 0;
493                 filteringaction_update_mark(info);
494                  */
495
496                 return TRUE;
497
498         case MATCHACTION_MARK:
499                 MSG_SET_PERM_FLAGS(info->flags, MSG_MARKED);
500                 debug_print("*** MARKING message %d, in folder %s, \"%s\"\n",
501                             info->msgnum, info->folder->name,
502                             info->subject ? info->subject : "<none>");
503                 filteringaction_update_mark(info);
504
505                 CHANGE_FLAGS(info);
506
507                 return TRUE;
508
509         case MATCHACTION_UNMARK:
510                 MSG_UNSET_PERM_FLAGS(info->flags, MSG_MARKED);
511                 filteringaction_update_mark(info);
512
513                 CHANGE_FLAGS(info);
514
515                 return TRUE;
516                 
517         case MATCHACTION_MARK_AS_READ:
518                 MSG_UNSET_PERM_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
519                 filteringaction_update_mark(info);
520
521                 CHANGE_FLAGS(info);
522
523                 return TRUE;
524
525         case MATCHACTION_MARK_AS_UNREAD:
526                 MSG_SET_PERM_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
527                 filteringaction_update_mark(info);
528
529                 CHANGE_FLAGS(info);
530                 
531                 return TRUE;
532
533         case MATCHACTION_FORWARD:
534
535                 account = account_find_from_id(action->account_id);
536                 compose = compose_forward(account, info, FALSE);
537                 if (compose->account->protocol == A_NNTP)
538                         compose_entry_append(compose, action->destination,
539                                              COMPOSE_NEWSGROUPS);
540                 else
541                         compose_entry_append(compose, action->destination,
542                                              COMPOSE_TO);
543
544                 val = compose_send(compose);
545                 if (val == 0) {
546                         gtk_widget_destroy(compose->window);
547                         return TRUE;
548                 }
549
550                 gtk_widget_destroy(compose->window);
551                 return FALSE;
552
553         case MATCHACTION_FORWARD_AS_ATTACHMENT:
554
555                 account = account_find_from_id(action->account_id);
556                 compose = compose_forward(account, info, TRUE);
557                 if (compose->account->protocol == A_NNTP)
558                         compose_entry_append(compose, action->destination,
559                                              COMPOSE_NEWSGROUPS);
560                 else
561                         compose_entry_append(compose, action->destination,
562                                              COMPOSE_TO);
563
564                 val = compose_send(compose);
565                 if (val == 0) {
566                         gtk_widget_destroy(compose->window);
567                         return TRUE;
568                 }
569
570                 gtk_widget_destroy(compose->window);
571                 return FALSE;
572
573         case MATCHACTION_EXECUTE:
574
575                 cmd = matching_build_command(action->destination, info);
576                 if (cmd == NULL)
577                         return TRUE;
578                 else {
579                         system(cmd);
580                         g_free(cmd);
581                 }
582
583                 return TRUE;
584
585         default:
586                 return FALSE;
587         }
588 }
589
590 /* filteringprop_apply() - runs the action on one MsgInfo if it matches the 
591  * criterium. certain actions can be followed by other actions. in this
592  * case the function returns FALSE. if an action can not be followed
593  * by others, the function returns TRUE.
594  *
595  * remember that this is because of the fact that msg flags are always
596  * _appended_ to mark files. currently sylpheed does not insert messages 
597  * at a certain index. 
598  * now, after having performed a certain action, the MsgInfo is still
599  * valid for the message. in *this* case the function returns FALSE.
600  */
601 static gboolean filteringprop_apply(FilteringProp * filtering, MsgInfo * info,
602                                     GHashTable *folder_table)
603 {
604         if (matcherlist_match(filtering->matchers, info)) {
605                 gboolean result;
606                 gchar   *action_str;
607                 gchar    buf[256]; 
608
609                 if (FALSE == (result = filteringaction_apply(filtering->action, info,
610                                                folder_table))) {
611                         action_str = filteringaction_to_string(buf, sizeof buf, filtering->action);
612                         g_warning(_("action %s could not be applied"), action_str);
613                 }
614
615                 switch(filtering->action->type) {
616                 case MATCHACTION_MOVE:
617                 case MATCHACTION_DELETE:
618                         return TRUE; /* MsgInfo invalid for message */
619                 case MATCHACTION_EXECUTE:
620                 case MATCHACTION_COPY:
621                 case MATCHACTION_MARK:
622                 case MATCHACTION_MARK_AS_READ:
623                 case MATCHACTION_UNMARK:
624                 case MATCHACTION_MARK_AS_UNREAD:
625                 case MATCHACTION_FORWARD:
626                 case MATCHACTION_FORWARD_AS_ATTACHMENT:
627                         return FALSE; /* MsgInfo still valid for message */
628                 default:
629                         return FALSE;
630                 }
631         }
632         else
633                 return FALSE;
634 }
635
636 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
637                     GHashTable *folder_table)
638 {
639         GSList * l;
640
641         if (info == NULL) {
642                 g_warning(_("msginfo is not set"));
643                 return;
644         }
645         
646         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
647                 FilteringProp * filtering = (FilteringProp *) l->data;
648                 
649                 if (filteringprop_apply(filtering, info, folder_table))
650                         break;
651         }
652 }
653
654 void filter_msginfo_move_or_delete(GSList * filtering_list, MsgInfo * info,
655                                    GHashTable *folder_table)
656 {
657         GSList * l;
658
659         if (info == NULL) {
660                 g_warning(_("msginfo is not set"));
661                 return;
662         }
663         
664         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
665                 FilteringProp * filtering = (FilteringProp *) l->data;
666
667                 switch (filtering->action->type) {
668                 case MATCHACTION_MOVE:
669                 case MATCHACTION_DELETE:
670                         if (filteringprop_apply(filtering, info, folder_table))
671                                 return;
672                 }
673         }
674 }
675
676 void filter_message(GSList * filtering_list, FolderItem * item,
677                     gint msgnum, GHashTable *folder_table)
678 {
679         MsgInfo * msginfo;
680         gchar * filename;
681         MsgFlags  msgflags = { 0, 0 };
682
683         if (item == NULL) {
684                 g_warning(_("folderitem not set"));
685                 return;
686         }
687
688         filename = folder_item_fetch_msg(item, msgnum);
689
690         if (filename == NULL) {
691                 g_warning(_("filename is not set"));
692                 return;
693         }
694
695         msginfo = procheader_parse(filename, msgflags, TRUE);
696         
697         g_free(filename);
698
699         if (msginfo == NULL) {
700                 g_warning(_("could not get info for %s"), filename);
701                 return;
702         }
703
704         msginfo->folder = item;
705         msginfo->msgnum = msgnum;
706
707         filter_msginfo(filtering_list, msginfo, folder_table);
708 }
709
710
711 /******************************************************************************/
712
713 /* revised filtering system.  
714  * 
715  * 07/18/01     alfons          initial revisement
716  */
717
718 /* (alfons)
719  * 
720  * the revised filtering system does not rely on a message being "registered"
721  * in one of sylpheed's folders. it now uses the file sylpheed created on
722  * incorporation of mail. 
723  * i do use MsgInfo data; that's needed by the matcher, but for the rest
724  * the MsgInfo is not really used. */
725  
726
727 /* add_mark() - adds a mark for a file */
728 static void add_mark(FolderItem *folder, gint msgnum, MsgPermFlags flags)
729 {
730         gchar * dest_path;
731         FILE * fp;
732
733         if (folder->folder->type == F_MH) {
734                 dest_path = folder_item_get_path(folder);
735                 if (!is_dir_exist(dest_path))
736                         make_dir_hier(dest_path);
737                 
738                 if (dest_path == NULL) {
739                         g_warning(_("Can't open mark file.\n"));
740                         return;
741                 }
742                 
743                 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
744                     == NULL) {
745                         g_warning(_("Can't open mark file.\n"));
746                         return;
747                 }
748
749                 /* TODO: straight from procmsg.c:procmsg_write_flags()
750                  * should update this when mark file version changes */
751 #if MARK_VERSION == 2            
752                 WRITE_CACHE_DATA_INT(msgnum, fp);
753                 WRITE_CACHE_DATA_INT(flags, fp);
754 #else
755 #error should rewrite the above for new  mark version   
756                 /* paste the source code of procmsg.c:procmsg_write_flags() */
757 #endif
758                 fclose(fp);
759                 return;
760         }
761         return;
762 }
763
764 /* prepare_destination() - prepares destination folder by registering it 
765  * in the global folder table (if not already there). it returns TRUE if
766  * successful. it also returns the FolderItem for the destination */
767 static gboolean prepare_destination(const gchar *destination, FolderItem **item, 
768                                     GHashTable *folder_table)
769 {
770         gint val;
771         FolderItem *result;
772
773         result = folder_find_item_from_identifier(destination);
774         if (!result)
775                 return FALSE;
776         if (folder_table) {
777                 *item = result;
778                 val = GPOINTER_TO_INT(g_hash_table_lookup(folder_table, *item));
779                 if (val == 0) {
780                         folder_item_scan(*item);
781                         g_hash_table_insert(folder_table, *item, GINT_TO_POINTER(1));
782                 }
783         }
784         return TRUE;
785 }
786
787 /* filter_incoming_perform_actions() - performs actions on incoming
788  * message. this function handles updating marks a little bit smarter;
789  * remember that marks can only be appended. */
790 static gboolean filter_incoming_perform_actions(FolderItem *default_folder, 
791                                                 MsgInfo    *msginfo,
792                                                 GHashTable *folder_table)
793 {
794         /* matching actions for msginfo */
795         struct matching_action {
796                 FilteringAction         *action;
797                 struct matching_action  *next;
798         };
799
800         struct matching_action  ma_head = { NULL, NULL };
801         struct matching_action *ma_tail = &ma_head;
802
803         /* need this for the forwarding stuff */
804         PrefsAccount *account;
805         Compose      *compose;
806         MsgFlags      tmp;
807         gchar        *fwd_msg_name;
808         gint          val;
809         MsgInfo      *fwd_msg;
810
811         MsgFlags     markflags = { 0, 0 };
812
813         /* use the global prefs_filtering list */
814         GSList *     list = global_processing;
815
816         /* things we get after having added a message to a folder */
817         FolderItem  *dest_folder;
818         gint         msgnum;
819         
820         /* after performing certain action, we may have to copy 
821          * the message to inbox too */
822         gboolean     copy_to_inbox_too = TRUE;
823         
824         /* filename is only put in msginfo->folder if MSG_FILTERING is set. */
825         const gchar *filename = (gchar *) msginfo->folder;
826         
827         MsgPermFlags flags;
828         gboolean     stop;
829         gchar        *cmd;
830
831         /* build list of matching actions */
832         for (; list != NULL; list = g_slist_next(list)) {
833                 FilteringProp *prop = (FilteringProp *) list->data;
834                 
835                 if (!matcherlist_match(prop->matchers, msginfo))
836                         continue;
837
838                 if (NULL == (ma_tail->next = alloca(sizeof(struct matching_action)))) {
839                         debug_print(_("action allocation error\n"));
840                         break;
841                 }
842
843                 debug_print("*** action %d\n", prop->action->type);
844
845                 ma_tail->action = prop->action;
846                 ma_tail->next->action = NULL;
847                 ma_tail->next->next = NULL;
848                 ma_tail = ma_tail->next;
849         }
850
851         debug_print("*** collecting marks\n");          
852         
853         /* collect all marks */
854         for (ma_tail = &ma_head, stop = FALSE; ma_tail->action && !stop; ma_tail = ma_tail->next) {
855                 switch (ma_tail->action->type) {
856                 
857                 case MATCHACTION_MARK:
858                         MSG_SET_PERM_FLAGS(markflags, MSG_MARKED);
859                         break;
860                         
861                 case MATCHACTION_UNMARK:
862                         MSG_UNSET_PERM_FLAGS(markflags, MSG_MARKED);
863                         break;
864                 
865                 case MATCHACTION_MARK_AS_READ:
866                         MSG_UNSET_PERM_FLAGS(markflags, MSG_UNREAD);
867                         break;
868
869                 case MATCHACTION_MARK_AS_UNREAD:                        
870                         MSG_SET_PERM_FLAGS(markflags, MSG_UNREAD);
871                         break;
872
873                 case MATCHACTION_COLOR:
874                         MSG_SET_COLORLABEL_VALUE(markflags, ma_tail->action->labelcolor);
875                         break;
876
877                 /* UNCONTINUABLE */
878                 case MATCHACTION_FORWARD:
879                 case MATCHACTION_FORWARD_AS_ATTACHMENT:
880                 case MATCHACTION_MOVE:
881                 case MATCHACTION_DELETE:
882                         stop = TRUE;
883                         break;
884                 
885                 default:
886                         break;
887                 }
888         }
889
890
891         /* msgnum & dest_folder should have valid values after a succesful
892          * drop; in this case there is a MsgInfo available */
893         msgnum      = -1;
894         dest_folder = NULL;
895
896         debug_print("*** performing actions\n"); 
897         
898         for (ma_tail = &ma_head ; ma_tail->action; ma_tail = ma_tail->next) {
899
900                 /* there are variables you have to use when defining new actions:
901                  *
902                  * copy_to_inbox_too - if the original message should be copied to the inbox 
903                  *                     (default_folder) too.
904                  * 
905                  * also note that after dropping it to a folder (folder_item_add_msg()) you have
906                  * to mark the message, just to make sure any defined mark actions are applied. */
907                  
908 #define ACTION  (ma_tail->action->type) 
909
910                 /* C O N T I N U A B L E */
911
912                 if (MATCHACTION_COPY == ACTION) {
913                         debug_print("*** performing copy\n");
914                         copy_to_inbox_too = TRUE;
915                         if (!prepare_destination(ma_tail->action->destination, &dest_folder, folder_table)) {
916                                 debug_print("Rule failed: unknown destination %s\n", ma_tail->action->destination);
917                                 continue; /* try next action */
918                         }
919                         if (0 > (msgnum = folder_item_add_msg(dest_folder, filename, FALSE))) {
920                                 debug_print(_("Rule failed: could not copy to folder %s\n"),
921                                             ma_tail->action->destination);
922                                 continue; /* try next action */     
923                         }   
924                         flags = msginfo->flags.perm_flags | markflags.perm_flags;
925                         add_mark(dest_folder, msgnum, flags);
926                 }               
927                 else if (MATCHACTION_EXECUTE == ACTION) {
928                         debug_print("*** performing exec\n");
929                         copy_to_inbox_too = TRUE;
930                         
931                         /* matching_build_command() knows about filtering */
932                         cmd = matching_build_command(ma_tail->action->destination, msginfo);
933                         if (cmd) { 
934                                 system(cmd);
935                                 g_free(cmd);
936                         }
937                         else
938                                 debug_print(_("Rule failed: no command line\n"));
939                 } 
940
941                 /* U N C O N T I N U A B L E */
942                 
943                 else if (MATCHACTION_FORWARD == ACTION
944                 ||       MATCHACTION_FORWARD_AS_ATTACHMENT == ACTION) {
945                         debug_print("*** performing forward\n");
946
947                         /* forwarding messages is complicated because there's currently no 
948                          * way to forward a message using "filenames"; you can only forward
949                          * a message if you have its MsgInfo. this means we have to drop
950                          * the message first */ 
951                         if (0 > (msgnum = folder_item_add_msg(default_folder, filename, FALSE))) {
952                                 debug_print(_("Rule failed: could not forward\n"));
953                                 copy_to_inbox_too = TRUE;
954                                 continue;
955                         }
956                                 
957                         flags = msginfo->flags.perm_flags | markflags.perm_flags;
958                         flags |= MSG_FORWARDED;
959                         add_mark(default_folder, msgnum, flags);
960
961                         /* grab the dropped message */
962                         fwd_msg_name = folder_item_fetch_msg(default_folder, msgnum);
963
964                         tmp.perm_flags = tmp.tmp_flags = 0;
965                         fwd_msg = procheader_parse(fwd_msg_name, tmp, TRUE);
966
967                         fwd_msg->folder = default_folder;
968                         fwd_msg->msgnum = msgnum;
969
970                         /* do the compose_XXX stuff */
971                         account = account_find_from_id(ma_tail->action->account_id);
972                         compose = compose_forward(account, fwd_msg, ACTION == MATCHACTION_FORWARD ? FALSE : TRUE);
973                         if (compose->account->protocol == A_NNTP)
974                                 compose_entry_append(compose, ma_tail->action->destination,
975                                                      COMPOSE_NEWSGROUPS);
976                         else
977                                 compose_entry_append(compose, ma_tail->action->destination,
978                                                      COMPOSE_TO);
979
980                         compose_send(compose);
981
982                         procmsg_msginfo_free(fwd_msg);
983                         
984                         gtk_widget_destroy(compose->window);
985                         break;
986                 }                       
987                 else if (MATCHACTION_DELETE == ACTION) {
988                         debug_print("*** performing delete\n");
989                         copy_to_inbox_too = FALSE;
990
991                         /* drop to Trash */
992                         dest_folder = folder_get_default_trash();
993                         msgnum = folder_item_add_msg(dest_folder, filename, FALSE);
994                         if (msgnum < 0) {
995                                 debug_print(_("Rule failed: could not move to trash"));
996                                 copy_to_inbox_too = TRUE;           
997                         }
998                         else {
999                                 flags = msginfo->flags.perm_flags | markflags.perm_flags;
1000                                 add_mark(dest_folder, msgnum, flags);
1001                         }
1002                         break;                          
1003                 }
1004                 else if (MATCHACTION_MOVE == ACTION) {
1005                         debug_print("*** performing move\n");
1006                         copy_to_inbox_too = FALSE;
1007                         
1008                         if (!prepare_destination(ma_tail->action->destination, &dest_folder, folder_table)) {
1009                                 copy_to_inbox_too = TRUE;
1010                                 break;
1011                         }
1012                                 
1013                         msgnum = folder_item_add_msg(dest_folder, filename, FALSE);
1014                         if (msgnum < 0) {
1015                                 debug_print(_("Rule failed: could not move to folder %s\n"),
1016                                             ma_tail->action->destination);
1017                                 copy_to_inbox_too = TRUE;                                          
1018                                 break;
1019                         }   
1020                         
1021                         flags = msginfo->flags.perm_flags | markflags.perm_flags;
1022                         add_mark(dest_folder, msgnum, flags);
1023                         break;
1024                 }
1025         }
1026
1027         /* may need to copy it to inbox too */
1028         if (copy_to_inbox_too) {
1029                 debug_print("*** performing inbox copy\n");
1030                 msgnum = folder_item_add_msg(default_folder, filename, TRUE);
1031                 if (msgnum < 0) {
1032                         debug_print(_("error copying incoming file %s to inbox\n"), 
1033                                     filename);
1034                         return FALSE;                               
1035                 }
1036                 flags = msginfo->flags.perm_flags | markflags.perm_flags;
1037                 add_mark(default_folder, msgnum, flags);
1038         }
1039         else {
1040                 debug_print("*** unlinking\n");
1041                 if (unlink(filename) < 0) 
1042                         debug_print(_("error deleting incoming message file\n"));
1043         }
1044
1045 #undef ACTION
1046
1047         return TRUE;
1048 }
1049
1050 static void filter_incoming_msginfo(FolderItem *default_folder, MsgInfo *msginfo, 
1051                                     GHashTable *folder_table)
1052 {
1053         filter_incoming_perform_actions(default_folder, msginfo, folder_table); 
1054 }
1055
1056 /* filter_incoming_message() - tries to apply a filter on one incoming message.
1057  * it also handles the case when there's no filter match */
1058 void filter_incoming_message(FolderItem *default_folder, const gchar *file_name, 
1059                              GHashTable *folder_table)
1060 {
1061         MsgInfo *msginfo;
1062         MsgFlags msgflags = { 0, 0 };
1063         
1064         /* make an "uncomplete" msginfo. it's incomplete because it doesn't
1065          * have a message number / folder yet. */
1066         if (NULL == (msginfo = procheader_parse(file_name, msgflags, TRUE))) {
1067                 g_warning(_("error filtering incoming message %s\n"), 
1068                           file_name);
1069                 return;         
1070         }
1071
1072         /* let matcher know that this is a message that has no
1073          * valid body data yet. */
1074         MSG_SET_TMP_FLAGS(msginfo->flags, MSG_FILTERING);
1075         msginfo->folder = (FolderItem *) g_strdup(file_name); /* actually storing a pointer to a string */
1076         msginfo->msgnum = 0;
1077
1078         filter_incoming_msginfo(default_folder, msginfo, folder_table);
1079
1080         g_free(msginfo->folder);
1081         procmsg_msginfo_free(msginfo);
1082 }
1083
1084 /****************************************************************************/
1085
1086
1087
1088 /*
1089 void prefs_filtering_read_config(void)
1090 {
1091
1092         gchar *rcpath;
1093         FILE *fp;
1094         gchar buf[PREFSBUFSIZE];
1095
1096         debug_print(_("Reading filtering configuration...\n"));
1097
1098         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1099                              FILTERING_RC, NULL);
1100         if ((fp = fopen(rcpath, "r")) == NULL) {
1101                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
1102                 g_free(rcpath);
1103                 prefs_filtering = NULL;
1104                 return;
1105         }
1106         g_free(rcpath);
1107         */
1108         /* remove all filtering */
1109         /*
1110         while (prefs_filtering != NULL) {
1111                 FilteringProp * filtering =
1112                         (FilteringProp *) prefs_filtering->data;
1113                 filteringprop_free(filtering);
1114                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1115         }
1116
1117         while (fgets(buf, sizeof(buf), fp) != NULL) {
1118                 FilteringProp * filtering;
1119                 gchar * tmp;
1120
1121                 g_strchomp(buf);
1122
1123                 if ((*buf != '#') && (*buf != '\0')) {
1124                         tmp = buf;
1125                         filtering = filteringprop_parse(&tmp);
1126                         if (tmp != NULL) {
1127                                 prefs_filtering =
1128                                         g_slist_append(prefs_filtering,
1129                                                        filtering);
1130                         }
1131                         else {
1132         */
1133                                 /* debug */
1134         /*
1135                                 g_warning(_("syntax error : %s\n"), buf);
1136                         }
1137                 }
1138         }
1139
1140         fclose(fp);
1141 }
1142 */
1143
1144 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
1145 {
1146         gchar *command_str;
1147
1148         command_str = get_matchparser_tab_str(action->type);
1149
1150         if (command_str == NULL)
1151                 return NULL;
1152
1153         switch(action->type) {
1154         case MATCHACTION_MOVE:
1155         case MATCHACTION_COPY:
1156         case MATCHACTION_EXECUTE:
1157                 g_snprintf(dest, destlen, "%s \"%s\"", command_str, action->destination);
1158                 return dest;
1159
1160         case MATCHACTION_DELETE:
1161         case MATCHACTION_MARK:
1162         case MATCHACTION_UNMARK:
1163         case MATCHACTION_MARK_AS_READ:
1164         case MATCHACTION_MARK_AS_UNREAD:
1165                 g_snprintf(dest, destlen, "%s", command_str);
1166                 return dest;
1167
1168         case MATCHACTION_FORWARD:
1169         case MATCHACTION_FORWARD_AS_ATTACHMENT:
1170                 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, action->destination); 
1171                 return dest; 
1172
1173         case MATCHACTION_COLOR:
1174                 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
1175                 return dest;  
1176
1177         default:
1178                 return NULL;
1179         }
1180 }
1181
1182 gchar * filteringprop_to_string(FilteringProp * prop)
1183 {
1184         gchar *list_str;
1185         gchar *action_str;
1186         gchar *filtering_str;
1187         gchar  buf[256];
1188
1189         action_str = filteringaction_to_string(buf, sizeof buf, prop->action);
1190
1191         if (action_str == NULL)
1192                 return NULL;
1193
1194         list_str = matcherlist_to_string(prop->matchers);
1195
1196         if (list_str == NULL)
1197                 return NULL;
1198
1199         filtering_str = g_strconcat(list_str, " ", action_str, NULL);
1200         g_free(list_str);
1201
1202         return filtering_str;
1203 }
1204
1205 void prefs_filtering_write_config(void)
1206 {
1207         /*
1208         gchar *rcpath;
1209         PrefFile *pfile;
1210         GSList *cur;
1211         FilteringProp * prop;
1212
1213         debug_print(_("Writing filtering configuration...\n"));
1214
1215         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
1216
1217         if ((pfile = prefs_write_open(rcpath)) == NULL) {
1218                 g_warning(_("failed to write configuration to file\n"));
1219                 g_free(rcpath);
1220                 return;
1221         }
1222
1223         for (cur = global_processing; cur != NULL; cur = cur->next) {
1224                 gchar *filtering_str;
1225
1226                 prop = (FilteringProp *) cur->data;
1227                 filtering_str = filteringprop_to_string(prop);
1228                 
1229                 if (fputs(filtering_str, pfile->fp) == EOF ||
1230                     fputc('\n', pfile->fp) == EOF) {
1231                         FILE_OP_ERROR(rcpath, "fputs || fputc");
1232                         prefs_write_close_revert(pfile);
1233                         g_free(rcpath);
1234                         g_free(filtering_str);
1235                         return;
1236                 }
1237                 g_free(filtering_str);
1238         }
1239
1240         g_free(rcpath);
1241
1242         if (prefs_write_close(pfile) < 0) {
1243                 g_warning(_("failed to write configuration to file\n"));
1244                 return;
1245         }
1246         */
1247 }
1248
1249 void prefs_filtering_free(GSList * prefs_filtering)
1250 {
1251         while (prefs_filtering != NULL) {
1252                 FilteringProp * filtering = (FilteringProp *)
1253                         prefs_filtering->data;
1254                 filteringprop_free(filtering);
1255                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1256         }
1257 }
1258
1259 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1260 {
1261         FolderItem *item = node->data;
1262
1263         if(!item->prefs)
1264                 return FALSE;
1265
1266         prefs_filtering_free(item->prefs->processing);
1267         item->prefs->processing = NULL;
1268
1269         return FALSE;
1270 }
1271
1272 void prefs_filtering_clear()
1273 {
1274         GList * cur;
1275
1276         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1277                 Folder *folder;
1278
1279                 folder = (Folder *) cur->data;
1280                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1281                                 prefs_filtering_free_func, NULL);
1282         }
1283
1284         prefs_filtering_free(global_processing);
1285         global_processing = NULL;
1286 }