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