sync with sylpheed 0.5.0pre4 release
[claws.git] / src / filtering.c
1 #include <ctype.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include <gtk/gtk.h>
6 #include <stdio.h>
7 #include "intl.h"
8 #include "utils.h"
9 #include "defs.h"
10 #include "procheader.h"
11 #include "matcher.h"
12 #include "filtering.h"
13 #include "prefs.h"
14 #include "compose.h"
15
16 #define PREFSBUFSIZE            1024
17
18 GSList * prefs_filtering = NULL;
19
20 FilteringAction * filteringaction_new(int type, int account_id,
21                                       gchar * destination)
22 {
23         FilteringAction * action;
24
25         action = g_new0(FilteringAction, 1);
26
27         action->type = type;
28         action->account_id = account_id;
29         if (destination)
30                 action->destination = g_strdup(destination);
31
32         return action;
33 }
34
35 void filteringaction_free(FilteringAction * action)
36 {
37         if (action->destination)
38                 g_free(action->destination);
39         g_free(action);
40 }
41
42 FilteringAction * filteringaction_parse(gchar ** str)
43 {
44         FilteringAction * action;
45         gchar * tmp;
46         gchar * destination = NULL;
47         gint account_id = 0;
48         gint key;
49
50         tmp = * str;
51
52         key = matcher_parse_keyword(&tmp);
53
54         switch (key) {
55         case MATCHING_ACTION_MOVE:
56         case MATCHING_ACTION_COPY:
57         case MATCHING_EXECUTE:
58                 destination = matcher_parse_str(&tmp);
59                 if (tmp == NULL) {
60                         * str = NULL;
61                         return NULL;
62                 }
63                 break;
64         case MATCHING_ACTION_DELETE:
65                 break;
66         case MATCHING_ACTION_MARK:
67                 break;
68         case MATCHING_ACTION_MARK_AS_READ:
69                 break;
70         case MATCHING_ACTION_UNMARK:
71                 break;
72         case MATCHING_ACTION_MARK_AS_UNREAD:
73                 break;
74         case MATCHING_ACTION_FORWARD:
75         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
76                 account_id = matcher_parse_number(&tmp);
77                 if (tmp == NULL) {
78                         * str = NULL;
79                         return NULL;
80                 }
81
82                 destination = matcher_parse_str(&tmp);
83                 if (tmp == NULL) {
84                         * str = NULL;
85                         return NULL;
86                 }
87
88                 break;
89         default:
90                 * str = NULL;
91                 return NULL;
92         }
93
94         * str = tmp;
95         action = filteringaction_new(key, account_id, destination);
96
97         return action;
98 }
99
100 FilteringProp * filteringprop_parse(gchar ** str)
101 {
102         gchar * tmp;
103         gint key;
104         FilteringProp * filtering;
105         MatcherList * matchers;
106         FilteringAction * action;
107         
108         tmp = * str;
109
110         matchers = matcherlist_parse(&tmp);
111         if (tmp == NULL) {
112                 * str = NULL;
113                 return NULL;
114         }
115
116         if (tmp == NULL) {
117                 matcherlist_free(matchers);
118                 * str = NULL;
119                 return NULL;
120         }
121
122         action = filteringaction_parse(&tmp);
123         if (tmp == NULL) {
124                 matcherlist_free(matchers);
125                 * str = NULL;
126                 return NULL;
127         }
128
129         filtering = filteringprop_new(matchers, action);
130
131         * str = tmp;
132         return filtering;
133 }
134
135
136 FilteringProp * filteringprop_new(MatcherList * matchers,
137                                   FilteringAction * action)
138 {
139         FilteringProp * filtering;
140
141         filtering = g_new0(FilteringProp, 1);
142         filtering->matchers = matchers;
143         filtering->action = action;
144
145         return filtering;
146 }
147
148 void filteringprop_free(FilteringProp * prop)
149 {
150         matcherlist_free(prop->matchers);
151         filteringaction_free(prop->action);
152         g_free(prop);
153 }
154
155 static gboolean filteringaction_update_mark(MsgInfo * info)
156 {
157         gchar * dest_path;
158         FILE * fp;
159
160         if (info->folder->folder->type == F_MH) {
161                 dest_path = folder_item_get_path(info->folder);
162                 if (!is_dir_exist(dest_path))
163                         make_dir_hier(dest_path);
164                 
165                 if (dest_path == NULL) {
166                         g_warning(_("Can't open mark file.\n"));
167                         return FALSE;
168                 }
169                 
170                 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
171                     == NULL) {
172                         g_warning(_("Can't open mark file.\n"));
173                         return FALSE;
174                 }
175                 
176                 procmsg_write_flags(info, fp);
177                 fclose(fp);
178                 return TRUE;
179         }
180         return FALSE;
181 }
182
183 static gchar * filteringaction_execute_command(gchar * cmd, MsgInfo * info)
184 {
185         gchar * s = cmd;
186         gchar * filename = NULL;
187         gchar * processed_cmd;
188         gchar * p;
189         gint size;
190
191         size = strlen(cmd) + 1;
192         while (*s != '\0') {
193                 if (*s == '%') {
194                         s++;
195                         switch (*s) {
196                         case '%':
197                                 size -= 1;
198                                 break;
199                         case 's': /* subject */
200                                 size += strlen(info->subject) - 2;
201                                 break;
202                         case 'f': /* from */
203                                 size += strlen(info->from) - 2;
204                                 break;
205                         case 't': /* to */
206                                 size += strlen(info->to) - 2;
207                                 break;
208                         case 'c': /* cc */
209                                 size += strlen(info->cc) - 2;
210                                 break;
211                         case 'd': /* date */
212                                 size += strlen(info->date) - 2;
213                                 break;
214                         case 'i': /* message-id */
215                                 size += strlen(info->msgid) - 2;
216                                 break;
217                         case 'n': /* newsgroups */
218                                 size += strlen(info->newsgroups) - 2;
219                                 break;
220                         case 'r': /* references */
221                                 size += strlen(info->references) - 2;
222                                 break;
223                         case 'F': /* file */
224                                 filename = folder_item_fetch_msg(info->folder,
225                                                                  info->msgnum);
226                                 
227                                 if (filename == NULL) {
228                                         g_warning(_("filename is not set"));
229                                         return NULL;
230                                 }
231                                 else
232                                         size += strlen(filename) - 2;
233                                 break;
234                         }
235                         s++;
236                 }
237                 else s++;
238         }
239
240
241         processed_cmd = g_new0(gchar, size);
242         s = cmd;
243         p = processed_cmd;
244
245         while (*s != '\0') {
246                 if (*s == '%') {
247                         s++;
248                         switch (*s) {
249                         case '%':
250                                 *p = '%';
251                                 p++;
252                                 break;
253                         case 's': /* subject */
254                                 if (info->subject != NULL)
255                                         strcpy(p, info->subject);
256                                 else
257                                         strcpy(p, "(none)");
258                                 p += strlen(p);
259                                 break;
260                         case 'f': /* from */
261                                 if (info->from != NULL)
262                                         strcpy(p, info->from);
263                                 else
264                                         strcpy(p, "(none)");
265                                 p += strlen(p);
266                                 break;
267                         case 't': /* to */
268                                 if (info->to != NULL)
269                                         strcpy(p, info->to);
270                                 else
271                                         strcpy(p, "(none)");
272                                 p += strlen(p);
273                                 break;
274                         case 'c': /* cc */
275                                 if (info->cc != NULL)
276                                         strcpy(p, info->cc);
277                                 else
278                                         strcpy(p, "(none)");
279                                 p += strlen(p);
280                                 break;
281                         case 'd': /* date */
282                                 if (info->date != NULL)
283                                         strcpy(p, info->date);
284                                 else
285                                         strcpy(p, "(none)");
286                                 p += strlen(p);
287                                 break;
288                         case 'i': /* message-id */
289                                 if (info->msgid != NULL)
290                                         strcpy(p, info->msgid);
291                                 else
292                                         strcpy(p, "(none)");
293                                 p += strlen(p);
294                                 break;
295                         case 'n': /* newsgroups */
296                                 if (info->newsgroups != NULL)
297                                         strcpy(p, info->newsgroups);
298                                 else
299                                         strcpy(p, "(none)");
300                                 p += strlen(p);
301                                 break;
302                         case 'r': /* references */
303                                 if (info->references != NULL)
304                                         strcpy(p, info->references);
305                                 else
306                                         strcpy(p, "(none)");
307                                 p += strlen(p);
308                                 break;
309                         case 'F': /* file */
310                                 strcpy(p, filename);
311                                 p += strlen(p);
312                                 break;
313                         default:
314                                 *p = '%';
315                                 p++;
316                                 *p = *s;
317                                 p++;
318                                 break;
319                         }
320                         s++;
321                 }
322                 else {
323                         *p = *s;
324                         p++;
325                         s++;
326                 }
327         }
328         return processed_cmd;
329 }
330
331 /*
332   fitleringaction_apply
333   runs the action on one MsgInfo
334   return value : return TRUE if the action could be applied
335 */
336
337 #define CHANGE_FLAGS(msginfo) \
338 { \
339 if (msginfo->folder->folder->change_flags != NULL) \
340 msginfo->folder->folder->change_flags(msginfo->folder->folder, \
341                                       msginfo->folder, \
342                                       msginfo); \
343 }
344
345 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info,
346                                       GHashTable *folder_table)
347 {
348         FolderItem * dest_folder;
349         gint val;
350         Compose * compose;
351         PrefsAccount * account;
352         gchar * cmd;
353
354         switch(action->type) {
355         case MATCHING_ACTION_MOVE:
356                 dest_folder =
357                         folder_find_item_from_identifier(action->destination);
358                 if (!dest_folder)
359                         return FALSE;
360
361                 if (folder_item_move_msg(dest_folder, info) == -1)
362                         return FALSE;
363
364                 info->flags = 0;
365                 filteringaction_update_mark(info);
366                 
367                 if (folder_table) {
368                         val = GPOINTER_TO_INT(g_hash_table_lookup
369                                               (folder_table, dest_folder));
370                         if (val == 0) {
371                                 folder_item_scan(dest_folder);
372                                 g_hash_table_insert(folder_table, dest_folder,
373                                                     GINT_TO_POINTER(1));
374                         }
375                         val = GPOINTER_TO_INT(g_hash_table_lookup
376                                               (folder_table, info->folder));
377                         if (val == 0) {
378                                 folder_item_scan(info->folder);
379                                 g_hash_table_insert(folder_table, info->folder,
380                                                     GINT_TO_POINTER(1));
381                         }
382                 }
383
384                 return TRUE;
385
386         case MATCHING_ACTION_COPY:
387                 dest_folder =
388                         folder_find_item_from_identifier(action->destination);
389                 if (!dest_folder)
390                         return FALSE;
391
392                 if (folder_item_copy_msg(dest_folder, info) == -1)
393                         return FALSE;
394
395                 if (folder_table) {
396                         val = GPOINTER_TO_INT(g_hash_table_lookup
397                                               (folder_table, dest_folder));
398                         if (val == 0) {
399                                 folder_item_scan(dest_folder);
400                                 g_hash_table_insert(folder_table, dest_folder,
401                                                     GINT_TO_POINTER(1));
402                         }
403                 }
404                         
405                 return TRUE;
406
407         case MATCHING_ACTION_DELETE:
408                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
409                         return FALSE;
410
411                 info->flags = 0;
412                 filteringaction_update_mark(info);
413
414                 return TRUE;
415
416         case MATCHING_ACTION_MARK:
417                 MSG_SET_FLAGS(info->flags, MSG_MARKED);
418                 filteringaction_update_mark(info);
419
420                 CHANGE_FLAGS(info);
421
422                 return TRUE;
423
424         case MATCHING_ACTION_UNMARK:
425                 MSG_UNSET_FLAGS(info->flags, MSG_MARKED);
426                 filteringaction_update_mark(info);
427
428                 CHANGE_FLAGS(info);
429
430                 return TRUE;
431                 
432         case MATCHING_ACTION_MARK_AS_READ:
433                 MSG_UNSET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
434                 filteringaction_update_mark(info);
435
436                 CHANGE_FLAGS(info);
437
438                 return TRUE;
439
440         case MATCHING_ACTION_MARK_AS_UNREAD:
441                 MSG_SET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
442                 filteringaction_update_mark(info);
443
444                 CHANGE_FLAGS(info);
445                 
446                 return TRUE;
447
448         case MATCHING_ACTION_FORWARD:
449
450                 account = account_find_from_id(action->account_id);
451                 compose = compose_forward(account, info, FALSE);
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 MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
469
470                 account = account_find_from_id(action->account_id);
471                 compose = compose_forward(account, info, TRUE);
472                 if (compose->account->protocol == A_NNTP)
473                         compose_entry_append(compose, action->destination,
474                                              COMPOSE_NEWSGROUPS);
475                 else
476                         compose_entry_append(compose, action->destination,
477                                              COMPOSE_TO);
478
479                 val = compose_send(compose);
480                 if (val == 0) {
481                         gtk_widget_destroy(compose->window);
482                         return TRUE;
483                 }
484
485                 gtk_widget_destroy(compose->window);
486                 return FALSE;
487
488         case MATCHING_EXECUTE:
489
490                 cmd = matching_build_command(action->destination, info);
491                 if (cmd == NULL)
492                         return TRUE;
493                 else {
494                         system(cmd);
495                         g_free(cmd);
496                 }
497
498                 return TRUE;
499
500         default:
501                 return FALSE;
502         }
503 }
504
505 /*
506   filteringprop_apply
507   runs the action on one MsgInfo if it matches the criterium
508   return value : return TRUE if the action doesn't allow more actions
509 */
510
511 static gboolean filteringprop_apply(FilteringProp * filtering, MsgInfo * info,
512                                     GHashTable *folder_table)
513 {
514         if (matcherlist_match(filtering->matchers, info)) {
515                 gint result;
516                 gchar * action_str;
517
518                 result = TRUE;
519
520                 result = filteringaction_apply(filtering->action, info,
521                                                folder_table);
522                 action_str =
523                         filteringaction_to_string(filtering->action);
524                 if (!result) {
525                         g_warning(_("action %s could not be applied"),
526                                   action_str);
527                 }
528                 else {
529                         debug_print(_("message %i %s..."),
530                                       info->msgnum, action_str);
531                 }
532
533                 g_free(action_str);
534
535                 switch(filtering->action->type) {
536                 case MATCHING_ACTION_MOVE:
537                 case MATCHING_ACTION_DELETE:
538                         return TRUE;
539                 case MATCHING_EXECUTE:
540                 case MATCHING_ACTION_COPY:
541                 case MATCHING_ACTION_MARK:
542                 case MATCHING_ACTION_MARK_AS_READ:
543                 case MATCHING_ACTION_UNMARK:
544                 case MATCHING_ACTION_MARK_AS_UNREAD:
545                 case MATCHING_ACTION_FORWARD:
546                 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
547                         return FALSE;
548                 default:
549                         return FALSE;
550                 }
551         }
552         else
553                 return FALSE;
554 }
555
556 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
557                     GHashTable *folder_table)
558 {
559         GSList * l;
560
561         if (info == NULL) {
562                 g_warning(_("msginfo is not set"));
563                 return;
564         }
565         
566         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
567                 FilteringProp * filtering = (FilteringProp *) l->data;
568                 
569                 if (filteringprop_apply(filtering, info, folder_table))
570                         break;
571         }
572 }
573
574 void filter_msginfo_move_or_delete(GSList * filtering_list, MsgInfo * info,
575                                    GHashTable *folder_table)
576 {
577         GSList * l;
578
579         if (info == NULL) {
580                 g_warning(_("msginfo is not set"));
581                 return;
582         }
583         
584         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
585                 FilteringProp * filtering = (FilteringProp *) l->data;
586
587                 switch (filtering->action->type) {
588                 case MATCHING_ACTION_MOVE:
589                 case MATCHING_ACTION_DELETE:
590                         if (filteringprop_apply(filtering, info, folder_table))
591                                 return;
592                 }
593         }
594 }
595
596 void filter_message(GSList * filtering_list, FolderItem * item,
597                     gint msgnum, GHashTable *folder_table)
598 {
599         MsgInfo * msginfo;
600         gchar * filename;
601
602         if (item == NULL) {
603                 g_warning(_("folderitem not set"));
604                 return;
605         }
606
607         filename = folder_item_fetch_msg(item, msgnum);
608
609         if (filename == NULL) {
610                 g_warning(_("filename is not set"));
611                 return;
612         }
613
614         msginfo = procheader_parse(filename, 0, TRUE);
615
616         g_free(filename);
617
618         if (msginfo == NULL) {
619                 g_warning(_("could not get info for %s"), filename);
620                 return;
621         }
622
623         msginfo->folder = item;
624         msginfo->msgnum = msgnum;
625
626         filter_msginfo(filtering_list, msginfo, folder_table);
627 }
628
629 void prefs_filtering_read_config(void)
630 {
631         gchar *rcpath;
632         FILE *fp;
633         gchar buf[PREFSBUFSIZE];
634
635         debug_print(_("Reading filtering configuration...\n"));
636
637         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
638                              FILTERING_RC, NULL);
639         if ((fp = fopen(rcpath, "r")) == NULL) {
640                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
641                 g_free(rcpath);
642                 prefs_filtering = NULL;
643                 return;
644         }
645         g_free(rcpath);
646
647         /* remove all filtering */
648         while (prefs_filtering != NULL) {
649                 FilteringProp * filtering =
650                         (FilteringProp *) prefs_filtering->data;
651                 filteringprop_free(filtering);
652                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
653         }
654
655         while (fgets(buf, sizeof(buf), fp) != NULL) {
656                 FilteringProp * filtering;
657                 gchar * tmp;
658
659                 g_strchomp(buf);
660
661                 if ((*buf != '#') && (*buf != '\0')) {
662                         tmp = buf;
663                         filtering = filteringprop_parse(&tmp);
664                         if (tmp != NULL) {
665                                 prefs_filtering =
666                                         g_slist_append(prefs_filtering,
667                                                        filtering);
668                         }
669                         else {
670                                 /* debug */
671                                 g_warning(_("syntax error : %s\n"), buf);
672                         }
673                 }
674         }
675
676         fclose(fp);
677 }
678
679 gchar * filteringaction_to_string(FilteringAction * action)
680 {
681         gchar * command_str;
682         gint i;
683         gchar * account_id_str;
684
685         command_str = NULL;
686         command_str = get_matchparser_tab_str(action->type);
687
688         if (command_str == NULL)
689                 return NULL;
690
691         switch(action->type) {
692         case MATCHING_ACTION_MOVE:
693         case MATCHING_ACTION_COPY:
694         case MATCHING_EXECUTE:
695                 return g_strconcat(command_str, " \"", action->destination,
696                                    "\"", NULL);
697
698         case MATCHING_ACTION_DELETE:
699         case MATCHING_ACTION_MARK:
700         case MATCHING_ACTION_UNMARK:
701         case MATCHING_ACTION_MARK_AS_READ:
702         case MATCHING_ACTION_MARK_AS_UNREAD:
703                 return g_strdup(command_str);
704                 break;
705
706         case MATCHING_ACTION_FORWARD:
707         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
708                 account_id_str = itos(action->account_id);
709                 return g_strconcat(command_str, " ", account_id_str,
710                                    " \"", action->destination, "\"", NULL);
711
712         default:
713                 return NULL;
714         }
715 }
716
717 gchar * filteringprop_to_string(FilteringProp * prop)
718 {
719         gchar * list_str;
720         gchar * action_str;
721         gchar * filtering_str;
722
723         action_str = filteringaction_to_string(prop->action);
724
725         if (action_str == NULL)
726                 return NULL;
727
728         list_str = matcherlist_to_string(prop->matchers);
729
730         if (list_str == NULL) {
731                 g_free(action_str);
732                 return NULL;
733         }
734
735         filtering_str = g_strconcat(list_str, " ", action_str, NULL);
736         g_free(list_str);
737         g_free(action_str);
738
739         return filtering_str;
740 }
741
742 void prefs_filtering_write_config(void)
743 {
744         gchar *rcpath;
745         PrefFile *pfile;
746         GSList *cur;
747         FilteringProp * prop;
748
749         debug_print(_("Writing filtering configuration...\n"));
750
751         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
752
753         if ((pfile = prefs_write_open(rcpath)) == NULL) {
754                 g_warning(_("failed to write configuration to file\n"));
755                 g_free(rcpath);
756                 return;
757         }
758
759         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
760                 gchar *filtering_str;
761
762                 prop = (FilteringProp *) cur->data;
763                 filtering_str = filteringprop_to_string(prop);
764                 if (fputs(filtering_str, pfile->fp) == EOF ||
765                     fputc('\n', pfile->fp) == EOF) {
766                         FILE_OP_ERROR(rcpath, "fputs || fputc");
767                         prefs_write_close_revert(pfile);
768                         g_free(rcpath);
769                         g_free(filtering_str);
770                         return;
771                 }
772                 g_free(filtering_str);
773         }
774
775         g_free(rcpath);
776
777         if (prefs_write_close(pfile) < 0) {
778                 g_warning(_("failed to write configuration to file\n"));
779                 return;
780         }
781 }