new matcher parser / bounce fix / rrr fixed
[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                 /* WRONG: can not update the mark, because the message has 
435                  * been moved. info pertains to original location. 
436                  * folder_item_move_msg() already updated the mark for the
437                  * destination folder.
438                 info->flags = 0;
439                 filteringaction_update_mark(info);
440                  */
441                 if (folder_table) {
442                         val = GPOINTER_TO_INT(g_hash_table_lookup
443                                               (folder_table, dest_folder));
444                         if (val == 0) {
445                                 folder_item_scan(dest_folder);
446                                 g_hash_table_insert(folder_table, dest_folder,
447                                                     GINT_TO_POINTER(1));
448                         }
449                         val = GPOINTER_TO_INT(g_hash_table_lookup
450                                               (folder_table, info->folder));
451                         if (val == 0) {
452                                 folder_item_scan(info->folder);
453                                 g_hash_table_insert(folder_table, info->folder,
454                                                     GINT_TO_POINTER(1));
455                         }
456                 }
457                 return TRUE;
458
459         case MATCHACTION_COPY:
460                 dest_folder =
461                         folder_find_item_from_identifier(action->destination);
462
463                 if (!dest_folder)
464                         return FALSE;
465
466                 /* NOTE: the following call *will* update the mark file for
467                  * the destination folder. but the original message will
468                  * still be there in the inbox. */
469
470                 if (folder_item_copy_msg(dest_folder, info) == -1)
471                         return FALSE;
472
473                 if (folder_table) {
474                         val = GPOINTER_TO_INT(g_hash_table_lookup
475                                               (folder_table, dest_folder));
476                         if (val == 0) {
477                                 folder_item_scan(dest_folder);
478                                 g_hash_table_insert(folder_table, dest_folder,
479                                                     GINT_TO_POINTER(1));
480                         }
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                 gint result;
606                 gchar * action_str;
607
608                 result = TRUE;
609
610                 result = filteringaction_apply(filtering->action, info,
611                                                folder_table);
612                 action_str =
613                         filteringaction_to_string(filtering->action);
614                 if (!result) {
615                         g_warning(_("action %s could not be applied"),
616                                   action_str);
617                 }
618                 else {
619                         debug_print(_("message %i %s..."),
620                                       info->msgnum, action_str);
621                 }
622
623                 g_free(action_str);
624
625                 switch(filtering->action->type) {
626                 case MATCHACTION_MOVE:
627                 case MATCHACTION_DELETE:
628                         return TRUE; /* MsgInfo invalid for message */
629                 case MATCHACTION_EXECUTE:
630                 case MATCHACTION_COPY:
631                 case MATCHACTION_MARK:
632                 case MATCHACTION_MARK_AS_READ:
633                 case MATCHACTION_UNMARK:
634                 case MATCHACTION_MARK_AS_UNREAD:
635                 case MATCHACTION_FORWARD:
636                 case MATCHACTION_FORWARD_AS_ATTACHMENT:
637                         return FALSE; /* MsgInfo still valid for message */
638                 default:
639                         return FALSE;
640                 }
641         }
642         else
643                 return FALSE;
644 }
645
646 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
647                     GHashTable *folder_table)
648 {
649         GSList * l;
650
651         if (info == NULL) {
652                 g_warning(_("msginfo is not set"));
653                 return;
654         }
655         
656         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
657                 FilteringProp * filtering = (FilteringProp *) l->data;
658                 
659                 if (filteringprop_apply(filtering, info, folder_table))
660                         break;
661         }
662 }
663
664 void filter_msginfo_move_or_delete(GSList * filtering_list, MsgInfo * info,
665                                    GHashTable *folder_table)
666 {
667         GSList * l;
668
669         if (info == NULL) {
670                 g_warning(_("msginfo is not set"));
671                 return;
672         }
673         
674         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
675                 FilteringProp * filtering = (FilteringProp *) l->data;
676
677                 switch (filtering->action->type) {
678                 case MATCHACTION_MOVE:
679                 case MATCHACTION_DELETE:
680                         if (filteringprop_apply(filtering, info, folder_table))
681                                 return;
682                 }
683         }
684 }
685
686 void filter_message(GSList * filtering_list, FolderItem * item,
687                     gint msgnum, GHashTable *folder_table)
688 {
689         MsgInfo * msginfo;
690         gchar * filename;
691         MsgFlags  msgflags = { 0, 0 };
692
693         if (item == NULL) {
694                 g_warning(_("folderitem not set"));
695                 return;
696         }
697
698         filename = folder_item_fetch_msg(item, msgnum);
699
700         if (filename == NULL) {
701                 g_warning(_("filename is not set"));
702                 return;
703         }
704
705         msginfo = procheader_parse(filename, msgflags, TRUE);
706         
707         g_free(filename);
708
709         if (msginfo == NULL) {
710                 g_warning(_("could not get info for %s"), filename);
711                 return;
712         }
713
714         msginfo->folder = item;
715         msginfo->msgnum = msgnum;
716
717         filter_msginfo(filtering_list, msginfo, folder_table);
718 }
719
720
721 /******************************************************************************/
722
723 /* revised filtering system.  
724  * 
725  * 07/18/01     alfons          initial revisement
726  */
727
728 /* (alfons)
729  * 
730  * the revised filtering system does not rely on a message being "registered"
731  * in one of sylpheed's folders. it now uses the file sylpheed created on
732  * incorporation of mail. 
733  * i do use MsgInfo data; that's needed by the matcher, but for the rest
734  * the MsgInfo is not really used. */
735  
736
737 /* add_mark() - adds a mark for a file */
738 static void add_mark(FolderItem *folder, gint msgnum, MsgPermFlags flags)
739 {
740         gchar * dest_path;
741         FILE * fp;
742
743         if (folder->folder->type == F_MH) {
744                 dest_path = folder_item_get_path(folder);
745                 if (!is_dir_exist(dest_path))
746                         make_dir_hier(dest_path);
747                 
748                 if (dest_path == NULL) {
749                         g_warning(_("Can't open mark file.\n"));
750                         return;
751                 }
752                 
753                 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
754                     == NULL) {
755                         g_warning(_("Can't open mark file.\n"));
756                         return;
757                 }
758
759                 /* TODO: straight from procmsg.c:procmsg_write_flags()
760                  * should update this when mark file version changes */
761 #if MARK_VERSION == 2            
762                 WRITE_CACHE_DATA_INT(msgnum, fp);
763                 WRITE_CACHE_DATA_INT(flags, fp);
764 #else
765 #error should rewrite the above for new  mark version   
766                 /* paste the source code of procmsg.c:procmsg_write_flags() */
767 #endif
768                 fclose(fp);
769                 return;
770         }
771         return;
772 }
773
774 /* prepare_destination() - prepares destination folder by registering it 
775  * in the global folder table (if not already there). it returns TRUE if
776  * successful. it also returns the FolderItem for the destination */
777 static gboolean prepare_destination(const gchar *destination, FolderItem **item, 
778                                     GHashTable *folder_table)
779 {
780         gint val;
781         FolderItem *result;
782
783         result = folder_find_item_from_identifier(destination);
784         if (!result)
785                 return FALSE;
786         if (folder_table) {
787                 *item = result;
788                 val = GPOINTER_TO_INT(g_hash_table_lookup(folder_table, *item));
789                 if (val == 0) {
790                         folder_item_scan(*item);
791                         g_hash_table_insert(folder_table, *item, GINT_TO_POINTER(1));
792                 }
793         }
794         return TRUE;
795 }
796
797 /* filter_incoming_perform_actions() - performs actions on incoming
798  * message. this function handles updating marks a little bit smarter;
799  * remember that marks can only be appended. */
800 static gboolean filter_incoming_perform_actions(FolderItem *default_folder, 
801                                                 MsgInfo    *msginfo,
802                                                 GHashTable *folder_table)
803 {
804         /* matching actions for msginfo */
805         struct matching_action {
806                 FilteringAction         *action;
807                 struct matching_action  *next;
808         };
809
810         struct matching_action  ma_head = { NULL, NULL };
811         struct matching_action *ma_tail = &ma_head;
812
813         /* need this for the forwarding stuff */
814         PrefsAccount *account;
815         Compose      *compose;
816         MsgFlags      tmp;
817         gchar        *fwd_msg_name;
818         gint          val;
819         MsgInfo      *fwd_msg;
820
821         MsgFlags     markflags = { 0, 0 };
822
823         /* use the global prefs_filtering list */
824         GSList *     list = global_processing;
825
826         /* things we get after having added a message to a folder */
827         FolderItem  *dest_folder;
828         gint         msgnum;
829         
830         /* after performing certain action, we may have to copy 
831          * the message to inbox too */
832         gboolean     copy_to_inbox_too = TRUE;
833         
834         /* filename is only put in msginfo->folder if MSG_FILTERING is set. */
835         const gchar *filename = (gchar *) msginfo->folder;
836         
837         MsgPermFlags flags;
838         gboolean     stop;
839         gchar        *cmd;
840
841         /* build list of matching actions */
842         for (; list != NULL; list = g_slist_next(list)) {
843                 FilteringProp *prop = (FilteringProp *) list->data;
844                 
845                 if (!matcherlist_match(prop->matchers, msginfo))
846                         continue;
847
848                 if (NULL == (ma_tail->next = alloca(sizeof(struct matching_action)))) {
849                         debug_print(_("action allocation error\n"));
850                         break;
851                 }
852
853                 debug_print("*** action %d\n", prop->action->type);
854
855                 ma_tail->action = prop->action;
856                 ma_tail->next->action = NULL;
857                 ma_tail->next->next = NULL;
858                 ma_tail = ma_tail->next;
859         }
860
861         debug_print("*** collecting marks\n");          
862         
863         /* collect all marks */
864         for (ma_tail = &ma_head, stop = FALSE; ma_tail->action && !stop; ma_tail = ma_tail->next) {
865                 switch (ma_tail->action->type) {
866                 
867                 case MATCHACTION_MARK:
868                         MSG_SET_PERM_FLAGS(markflags, MSG_MARKED);
869                         break;
870                         
871                 case MATCHACTION_UNMARK:
872                         MSG_UNSET_PERM_FLAGS(markflags, MSG_MARKED);
873                         break;
874                 
875                 case MATCHACTION_MARK_AS_READ:
876                         MSG_UNSET_PERM_FLAGS(markflags, MSG_UNREAD);
877                         break;
878
879                 case MATCHACTION_MARK_AS_UNREAD:                        
880                         MSG_SET_PERM_FLAGS(markflags, MSG_UNREAD);
881                         break;
882
883                 case MATCHACTION_COLOR:
884                         MSG_SET_COLORLABEL_VALUE(markflags, ma_tail->action->labelcolor);
885                         break;
886
887                 /* UNCONTINUABLE */
888                 case MATCHACTION_FORWARD:
889                 case MATCHACTION_FORWARD_AS_ATTACHMENT:
890                 case MATCHACTION_MOVE:
891                 case MATCHACTION_DELETE:
892                         stop = TRUE;
893                         break;
894                 
895                 default:
896                         break;
897                 }
898         }
899
900
901         /* msgnum & dest_folder should have valid values after a succesful
902          * drop; in this case there is a MsgInfo available */
903         msgnum      = -1;
904         dest_folder = NULL;
905
906         debug_print("*** performing actions\n"); 
907         
908         for (ma_tail = &ma_head ; ma_tail->action; ma_tail = ma_tail->next) {
909
910                 /* there are variables you have to use when defining new actions:
911                  *
912                  * copy_to_inbox_too - if the original message should be copied to the inbox 
913                  *                     (default_folder) too.
914                  * 
915                  * also note that after dropping it to a folder (folder_item_add_msg()) you have
916                  * to mark the message, just to make sure any defined mark actions are applied. */
917                  
918 #define ACTION  (ma_tail->action->type) 
919
920                 /* C O N T I N U A B L E */
921
922                 if (MATCHACTION_COPY == ACTION) {
923                         debug_print("*** performing copy\n");
924                         copy_to_inbox_too = TRUE;
925                         if (!prepare_destination(ma_tail->action->destination, &dest_folder, folder_table)) {
926                                 debug_print("Rule failed: unknown destination %s\n", ma_tail->action->destination);
927                                 continue; /* try next action */
928                         }
929                         if (0 > (msgnum = folder_item_add_msg(dest_folder, filename, FALSE))) {
930                                 debug_print(_("Rule failed: could not copy to folder %s\n"),
931                                             ma_tail->action->destination);
932                                 continue; /* try next action */     
933                         }   
934                         flags = msginfo->flags.perm_flags | markflags.perm_flags;
935                         add_mark(dest_folder, msgnum, flags);
936                 }               
937                 else if (MATCHACTION_EXECUTE == ACTION) {
938                         debug_print("*** performing exec\n");
939                         copy_to_inbox_too = TRUE;
940                         
941                         /* matching_build_command() knows about filtering */
942                         cmd = matching_build_command(ma_tail->action->destination, msginfo);
943                         if (cmd) { 
944                                 system(cmd);
945                                 g_free(cmd);
946                         }
947                         else
948                                 debug_print(_("Rule failed: no command line\n"));
949                 } 
950
951                 /* U N C O N T I N U A B L E */
952                 
953                 else if (MATCHACTION_FORWARD == ACTION
954                 ||       MATCHACTION_FORWARD_AS_ATTACHMENT == ACTION) {
955                         debug_print("*** performing forward\n");
956
957                         /* forwarding messages is complicated because there's currently no 
958                          * way to forward a message using "filenames"; you can only forward
959                          * a message if you have its MsgInfo. this means we have to drop
960                          * the message first */ 
961                         if (0 > (msgnum = folder_item_add_msg(default_folder, filename, FALSE))) {
962                                 debug_print(_("Rule failed: could not forward\n"));
963                                 copy_to_inbox_too = TRUE;
964                                 continue;
965                         }
966                                 
967                         flags = msginfo->flags.perm_flags | markflags.perm_flags;
968                         flags |= MSG_FORWARDED;
969                         add_mark(default_folder, msgnum, flags);
970
971                         /* grab the dropped message */
972                         fwd_msg_name = folder_item_fetch_msg(default_folder, msgnum);
973
974                         tmp.perm_flags = tmp.tmp_flags = 0;
975                         fwd_msg = procheader_parse(fwd_msg_name, tmp, TRUE);
976
977                         fwd_msg->folder = default_folder;
978                         fwd_msg->msgnum = msgnum;
979
980                         /* do the compose_XXX stuff */
981                         account = account_find_from_id(ma_tail->action->account_id);
982                         compose = compose_forward(account, fwd_msg, ACTION == MATCHACTION_FORWARD ? FALSE : TRUE);
983                         if (compose->account->protocol == A_NNTP)
984                                 compose_entry_append(compose, ma_tail->action->destination,
985                                                      COMPOSE_NEWSGROUPS);
986                         else
987                                 compose_entry_append(compose, ma_tail->action->destination,
988                                                      COMPOSE_TO);
989
990                         compose_send(compose);
991
992                         procmsg_msginfo_free(fwd_msg);
993                         
994                         gtk_widget_destroy(compose->window);
995                         break;
996                 }                       
997                 else if (MATCHACTION_DELETE == ACTION) {
998                         debug_print("*** performing delete\n");
999                         copy_to_inbox_too = FALSE;
1000
1001                         /* drop to Trash */
1002                         dest_folder = folder_get_default_trash();
1003                         msgnum = folder_item_add_msg(dest_folder, filename, FALSE);
1004                         if (msgnum < 0) {
1005                                 debug_print(_("Rule failed: could not move to trash"));
1006                                 copy_to_inbox_too = TRUE;           
1007                         }
1008                         else {
1009                                 flags = msginfo->flags.perm_flags | markflags.perm_flags;
1010                                 add_mark(dest_folder, msgnum, flags);
1011                         }
1012                         break;                          
1013                 }
1014                 else if (MATCHACTION_MOVE == ACTION) {
1015                         debug_print("*** performing move\n");
1016                         copy_to_inbox_too = FALSE;
1017                         
1018                         if (!prepare_destination(ma_tail->action->destination, &dest_folder, folder_table)) {
1019                                 copy_to_inbox_too = TRUE;
1020                                 break;
1021                         }
1022                                 
1023                         msgnum = folder_item_add_msg(dest_folder, filename, FALSE);
1024                         if (msgnum < 0) {
1025                                 debug_print(_("Rule failed: could not move to folder %s\n"),
1026                                             ma_tail->action->destination);
1027                                 copy_to_inbox_too = TRUE;                                          
1028                                 break;
1029                         }   
1030                         
1031                         flags = msginfo->flags.perm_flags | markflags.perm_flags;
1032                         add_mark(dest_folder, msgnum, flags);
1033                         break;
1034                 }
1035         }
1036
1037         /* may need to copy it to inbox too */
1038         if (copy_to_inbox_too) {
1039                 debug_print("*** performing inbox copy\n");
1040                 msgnum = folder_item_add_msg(default_folder, filename, TRUE);
1041                 if (msgnum < 0) {
1042                         debug_print(_("error copying incoming file %s to inbox\n"), 
1043                                     filename);
1044                         return FALSE;                               
1045                 }
1046                 flags = msginfo->flags.perm_flags | markflags.perm_flags;
1047                 add_mark(default_folder, msgnum, flags);
1048         }
1049         else {
1050                 debug_print("*** unlinking\n");
1051                 if (unlink(filename) < 0) 
1052                         debug_print(_("error deleting incoming message file\n"));
1053         }
1054
1055 #undef ACTION
1056
1057         return TRUE;
1058 }
1059
1060 static void filter_incoming_msginfo(FolderItem *default_folder, MsgInfo *msginfo, 
1061                                     GHashTable *folder_table)
1062 {
1063         filter_incoming_perform_actions(default_folder, msginfo, folder_table); 
1064 }
1065
1066 /* filter_incoming_message() - tries to apply a filter on one incoming message.
1067  * it also handles the case when there's no filter match */
1068 void filter_incoming_message(FolderItem *default_folder, const gchar *file_name, 
1069                              GHashTable *folder_table)
1070 {
1071         MsgInfo *msginfo;
1072         MsgFlags msgflags = { 0, 0 };
1073         
1074         /* make an "uncomplete" msginfo. it's incomplete because it doesn't
1075          * have a message number / folder yet. */
1076         if (NULL == (msginfo = procheader_parse(file_name, msgflags, TRUE))) {
1077                 g_warning(_("error filtering incoming message %s\n"), 
1078                           file_name);
1079                 return;         
1080         }
1081
1082         /* let matcher know that this is a message that has no
1083          * valid body data yet. */
1084         MSG_SET_TMP_FLAGS(msginfo->flags, MSG_FILTERING);
1085         msginfo->folder = (FolderItem *) g_strdup(file_name); /* actually storing a pointer to a string */
1086         msginfo->msgnum = 0;
1087
1088         filter_incoming_msginfo(default_folder, msginfo, folder_table);
1089
1090         g_free(msginfo->folder);
1091         procmsg_msginfo_free(msginfo);
1092 }
1093
1094 /****************************************************************************/
1095
1096
1097
1098 /*
1099 void prefs_filtering_read_config(void)
1100 {
1101
1102         gchar *rcpath;
1103         FILE *fp;
1104         gchar buf[PREFSBUFSIZE];
1105
1106         debug_print(_("Reading filtering configuration...\n"));
1107
1108         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1109                              FILTERING_RC, NULL);
1110         if ((fp = fopen(rcpath, "r")) == NULL) {
1111                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
1112                 g_free(rcpath);
1113                 prefs_filtering = NULL;
1114                 return;
1115         }
1116         g_free(rcpath);
1117         */
1118         /* remove all filtering */
1119         /*
1120         while (prefs_filtering != NULL) {
1121                 FilteringProp * filtering =
1122                         (FilteringProp *) prefs_filtering->data;
1123                 filteringprop_free(filtering);
1124                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1125         }
1126
1127         while (fgets(buf, sizeof(buf), fp) != NULL) {
1128                 FilteringProp * filtering;
1129                 gchar * tmp;
1130
1131                 g_strchomp(buf);
1132
1133                 if ((*buf != '#') && (*buf != '\0')) {
1134                         tmp = buf;
1135                         filtering = filteringprop_parse(&tmp);
1136                         if (tmp != NULL) {
1137                                 prefs_filtering =
1138                                         g_slist_append(prefs_filtering,
1139                                                        filtering);
1140                         }
1141                         else {
1142         */
1143                                 /* debug */
1144         /*
1145                                 g_warning(_("syntax error : %s\n"), buf);
1146                         }
1147                 }
1148         }
1149
1150         fclose(fp);
1151 }
1152 */
1153
1154 gchar * filteringaction_to_string(FilteringAction * action)
1155 {
1156         gchar * command_str;
1157         gint i;
1158         gchar * account_id_str;
1159         gchar * labelcolor_str;
1160
1161         /* FIXME: use g_sprintf() throughout */
1162
1163         command_str = NULL;
1164         command_str = get_matchparser_tab_str(action->type);
1165
1166         if (command_str == NULL)
1167                 return NULL;
1168
1169         switch(action->type) {
1170         case MATCHACTION_MOVE:
1171         case MATCHACTION_COPY:
1172         case MATCHACTION_EXECUTE:
1173                 return g_strconcat(command_str, " \"", action->destination,
1174                                    "\"", NULL);
1175
1176         case MATCHACTION_DELETE:
1177         case MATCHACTION_MARK:
1178         case MATCHACTION_UNMARK:
1179         case MATCHACTION_MARK_AS_READ:
1180         case MATCHACTION_MARK_AS_UNREAD:
1181                 return g_strdup(command_str);
1182                 break;
1183
1184         case MATCHACTION_FORWARD:
1185         case MATCHACTION_FORWARD_AS_ATTACHMENT:
1186                 account_id_str = itos(action->account_id);
1187                 return g_strconcat(command_str, " ", account_id_str,
1188                                    " \"", action->destination, "\"", NULL);
1189
1190         case MATCHACTION_COLOR:
1191                 labelcolor_str = itos(action->labelcolor);
1192                 return g_strconcat(command_str, " ", labelcolor_str, NULL);  
1193
1194         default:
1195                 return NULL;
1196         }
1197 }
1198
1199 gchar * filteringprop_to_string(FilteringProp * prop)
1200 {
1201         gchar * list_str;
1202         gchar * action_str;
1203         gchar * filtering_str;
1204
1205         action_str = filteringaction_to_string(prop->action);
1206
1207         if (action_str == NULL)
1208                 return NULL;
1209
1210         list_str = matcherlist_to_string(prop->matchers);
1211
1212         if (list_str == NULL) {
1213                 g_free(action_str);
1214                 return NULL;
1215         }
1216
1217         filtering_str = g_strconcat(list_str, " ", action_str, NULL);
1218         g_free(list_str);
1219         g_free(action_str);
1220
1221         return filtering_str;
1222 }
1223
1224 void prefs_filtering_write_config(void)
1225 {
1226         /*
1227         gchar *rcpath;
1228         PrefFile *pfile;
1229         GSList *cur;
1230         FilteringProp * prop;
1231
1232         debug_print(_("Writing filtering configuration...\n"));
1233
1234         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
1235
1236         if ((pfile = prefs_write_open(rcpath)) == NULL) {
1237                 g_warning(_("failed to write configuration to file\n"));
1238                 g_free(rcpath);
1239                 return;
1240         }
1241
1242         for (cur = global_processing; cur != NULL; cur = cur->next) {
1243                 gchar *filtering_str;
1244
1245                 prop = (FilteringProp *) cur->data;
1246                 filtering_str = filteringprop_to_string(prop);
1247                 
1248                 if (fputs(filtering_str, pfile->fp) == EOF ||
1249                     fputc('\n', pfile->fp) == EOF) {
1250                         FILE_OP_ERROR(rcpath, "fputs || fputc");
1251                         prefs_write_close_revert(pfile);
1252                         g_free(rcpath);
1253                         g_free(filtering_str);
1254                         return;
1255                 }
1256                 g_free(filtering_str);
1257         }
1258
1259         g_free(rcpath);
1260
1261         if (prefs_write_close(pfile) < 0) {
1262                 g_warning(_("failed to write configuration to file\n"));
1263                 return;
1264         }
1265         */
1266 }
1267
1268 void prefs_filtering_free(GSList * prefs_filtering)
1269 {
1270         while (prefs_filtering != NULL) {
1271                 FilteringProp * filtering = (FilteringProp *)
1272                         prefs_filtering->data;
1273                 filteringprop_free(filtering);
1274                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1275         }
1276 }
1277
1278 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1279 {
1280         FolderItem *item = node->data;
1281
1282         if(!item->prefs)
1283                 return FALSE;
1284
1285         prefs_filtering_free(item->prefs->processing);
1286         item->prefs->processing = NULL;
1287
1288         return FALSE;
1289 }
1290
1291 void prefs_filtering_clear()
1292 {
1293         GList * cur;
1294
1295         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1296                 Folder *folder;
1297
1298                 folder = (Folder *) cur->data;
1299                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1300                                 prefs_filtering_free_func, NULL);
1301         }
1302
1303         prefs_filtering_free(global_processing);
1304         global_processing = NULL;
1305 }