2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2001 Hiroyuki Yamamoto & The Sylpheed Claws Team
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.
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.
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.
20 /* (alfons) - Just a quick note of how this filtering module works on
21 * new (arriving) messages.
23 * 1) as an initialization step, code in inc.c and mbox.c set up the
24 * drop folder to the inbox (see inc.c and mbox.c).
26 * 2) the message is actually being copied to the drop folder using
27 * folder_item_add_msg(dropfolder, file, TRUE). this function
28 * eventually calls mh->add_msg(). however, the important thing
29 * about this function is, is that the folder is not yet updated
30 * to reflect the copy. i don't know about the validity of this
31 * assumption, however, the filtering code assumes this and
32 * updates the marks itself.
34 * 3) technically there's nothing wrong with the matcher (the
35 * piece of code which matches search strings). there's
36 * one gotcha in procmsg.c:procmsg_get_message_file(): it
37 * only reads a message file based on a MsgInfo. for design
38 * reasons the filtering system should read directly from
39 * a file (based on the file's name).
41 * 4) after the matcher sorts out any matches, it looks at the
42 * action. this part again pushes the folder system design
43 * to its limits. based on the assumption in 2), the matcher
44 * knows the message has not been added to the folder system yet.
45 * it can happily update mark files, and in fact it does.
58 #include "procheader.h"
60 #include "filtering.h"
64 #define PREFSBUFSIZE 1024
66 GSList * prefs_filtering = NULL;
68 FilteringAction * filteringaction_new(int type, int account_id,
71 FilteringAction * action;
73 action = g_new0(FilteringAction, 1);
76 action->account_id = account_id;
78 action->destination = g_strdup(destination);
83 void filteringaction_free(FilteringAction * action)
85 if (action->destination)
86 g_free(action->destination);
90 FilteringAction * filteringaction_parse(gchar ** str)
92 FilteringAction * action;
94 gchar * destination = NULL;
100 key = matcher_parse_keyword(&tmp);
103 case MATCHING_ACTION_MOVE:
104 case MATCHING_ACTION_COPY:
105 case MATCHING_EXECUTE:
106 destination = matcher_parse_str(&tmp);
112 case MATCHING_ACTION_DELETE:
114 case MATCHING_ACTION_MARK:
116 case MATCHING_ACTION_MARK_AS_READ:
118 case MATCHING_ACTION_UNMARK:
120 case MATCHING_ACTION_MARK_AS_UNREAD:
122 case MATCHING_ACTION_FORWARD:
123 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
124 account_id = matcher_parse_number(&tmp);
130 destination = matcher_parse_str(&tmp);
143 action = filteringaction_new(key, account_id, destination);
148 FilteringProp * filteringprop_parse(gchar ** str)
152 FilteringProp * filtering;
153 MatcherList * matchers;
154 FilteringAction * action;
158 matchers = matcherlist_parse(&tmp);
165 matcherlist_free(matchers);
170 action = filteringaction_parse(&tmp);
172 matcherlist_free(matchers);
177 filtering = filteringprop_new(matchers, action);
184 FilteringProp * filteringprop_new(MatcherList * matchers,
185 FilteringAction * action)
187 FilteringProp * filtering;
189 filtering = g_new0(FilteringProp, 1);
190 filtering->matchers = matchers;
191 filtering->action = action;
196 void filteringprop_free(FilteringProp * prop)
198 matcherlist_free(prop->matchers);
199 filteringaction_free(prop->action);
203 /* filteringaction_update_mark() - updates a mark for a message. note that
204 * the message should not have been moved or copied. remember that the
205 * procmsg_open_mark_file(PATH, TRUE) actually _appends_ a new record.
207 static gboolean filteringaction_update_mark(MsgInfo * info)
212 if (info->folder->folder->type == F_MH) {
213 dest_path = folder_item_get_path(info->folder);
214 if (!is_dir_exist(dest_path))
215 make_dir_hier(dest_path);
217 if (dest_path == NULL) {
218 g_warning(_("Can't open mark file.\n"));
222 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
224 g_warning(_("Can't open mark file.\n"));
228 procmsg_write_flags(info, fp);
235 static gchar * filteringaction_execute_command(gchar * cmd, MsgInfo * info)
238 gchar * filename = NULL;
239 gchar * processed_cmd;
243 size = strlen(cmd) + 1;
251 case 's': /* subject */
252 size += strlen(info->subject) - 2;
255 size += strlen(info->from) - 2;
258 size += strlen(info->to) - 2;
261 size += strlen(info->cc) - 2;
264 size += strlen(info->date) - 2;
266 case 'i': /* message-id */
267 size += strlen(info->msgid) - 2;
269 case 'n': /* newsgroups */
270 size += strlen(info->newsgroups) - 2;
272 case 'r': /* references */
273 size += strlen(info->references) - 2;
276 filename = folder_item_fetch_msg(info->folder,
279 if (filename == NULL) {
280 g_warning(_("filename is not set"));
284 size += strlen(filename) - 2;
293 processed_cmd = g_new0(gchar, size);
305 case 's': /* subject */
306 if (info->subject != NULL)
307 strcpy(p, info->subject);
313 if (info->from != NULL)
314 strcpy(p, info->from);
320 if (info->to != NULL)
327 if (info->cc != NULL)
334 if (info->date != NULL)
335 strcpy(p, info->date);
340 case 'i': /* message-id */
341 if (info->msgid != NULL)
342 strcpy(p, info->msgid);
347 case 'n': /* newsgroups */
348 if (info->newsgroups != NULL)
349 strcpy(p, info->newsgroups);
354 case 'r': /* references */
355 if (info->references != NULL)
356 strcpy(p, info->references);
380 return processed_cmd;
384 fitleringaction_apply
385 runs the action on one MsgInfo
386 return value : return TRUE if the action could be applied
389 #define CHANGE_FLAGS(msginfo) \
391 if (msginfo->folder->folder->change_flags != NULL) \
392 msginfo->folder->folder->change_flags(msginfo->folder->folder, \
397 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info,
398 GHashTable *folder_table)
400 FolderItem * dest_folder;
403 PrefsAccount * account;
406 switch(action->type) {
407 case MATCHING_ACTION_MOVE:
409 folder_find_item_from_identifier(action->destination);
413 if (folder_item_move_msg(dest_folder, info) == -1)
416 /* WRONG: can not update the mark, because the message has
417 * been moved. info pertains to original location.
418 * folder_item_move_msg() already updated the mark for the
419 * destination folder.
421 filteringaction_update_mark(info);
425 val = GPOINTER_TO_INT(g_hash_table_lookup
426 (folder_table, dest_folder));
428 folder_item_scan(dest_folder);
429 g_hash_table_insert(folder_table, dest_folder,
432 val = GPOINTER_TO_INT(g_hash_table_lookup
433 (folder_table, info->folder));
435 folder_item_scan(info->folder);
436 g_hash_table_insert(folder_table, info->folder,
443 case MATCHING_ACTION_COPY:
445 folder_find_item_from_identifier(action->destination);
450 /* NOTE: the following call *will* update the mark file for
451 * the destination folder. but the original message will
452 * still be there in the inbox. */
454 if (folder_item_copy_msg(dest_folder, info) == -1)
458 val = GPOINTER_TO_INT(g_hash_table_lookup
459 (folder_table, dest_folder));
461 folder_item_scan(dest_folder);
462 g_hash_table_insert(folder_table, dest_folder,
469 case MATCHING_ACTION_DELETE:
470 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
473 /* WRONG: can not update the mark. this would actually add
474 * a bogus record to the mark file for the message's original
477 filteringaction_update_mark(info);
482 case MATCHING_ACTION_MARK:
483 MSG_SET_FLAGS(info->flags, MSG_MARKED);
484 filteringaction_update_mark(info);
490 case MATCHING_ACTION_UNMARK:
491 MSG_UNSET_FLAGS(info->flags, MSG_MARKED);
492 filteringaction_update_mark(info);
498 case MATCHING_ACTION_MARK_AS_READ:
499 MSG_UNSET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
500 filteringaction_update_mark(info);
506 case MATCHING_ACTION_MARK_AS_UNREAD:
507 MSG_SET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
508 filteringaction_update_mark(info);
514 case MATCHING_ACTION_FORWARD:
516 account = account_find_from_id(action->account_id);
517 compose = compose_forward(account, info, FALSE);
518 if (compose->account->protocol == A_NNTP)
519 compose_entry_append(compose, action->destination,
522 compose_entry_append(compose, action->destination,
525 val = compose_send(compose);
527 gtk_widget_destroy(compose->window);
531 gtk_widget_destroy(compose->window);
534 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
536 account = account_find_from_id(action->account_id);
537 compose = compose_forward(account, info, TRUE);
538 if (compose->account->protocol == A_NNTP)
539 compose_entry_append(compose, action->destination,
542 compose_entry_append(compose, action->destination,
545 val = compose_send(compose);
547 gtk_widget_destroy(compose->window);
551 gtk_widget_destroy(compose->window);
554 case MATCHING_EXECUTE:
556 cmd = matching_build_command(action->destination, info);
571 /* filteringprop_apply() - runs the action on one MsgInfo if it matches the
572 * criterium. certain actions can be followed by other actions. in this
573 * case the function returns FALSE. if an action can not be followed
574 * by others, the function returns TRUE.
576 * remember that this is because of the fact that msg flags are always
577 * _appended_ to mark files. currently sylpheed does not insert messages
578 * at a certain index.
579 * now, after having performed a certain action, the MsgInfo is still
580 * valid for the message. in *this* case the function returns FALSE.
582 static gboolean filteringprop_apply(FilteringProp * filtering, MsgInfo * info,
583 GHashTable *folder_table)
585 if (matcherlist_match(filtering->matchers, info)) {
591 result = filteringaction_apply(filtering->action, info,
594 filteringaction_to_string(filtering->action);
596 g_warning(_("action %s could not be applied"),
600 debug_print(_("message %i %s..."),
601 info->msgnum, action_str);
606 switch(filtering->action->type) {
607 case MATCHING_ACTION_MOVE:
608 case MATCHING_ACTION_DELETE:
609 return TRUE; /* MsgInfo invalid for message */
610 case MATCHING_EXECUTE:
611 case MATCHING_ACTION_COPY:
612 case MATCHING_ACTION_MARK:
613 case MATCHING_ACTION_MARK_AS_READ:
614 case MATCHING_ACTION_UNMARK:
615 case MATCHING_ACTION_MARK_AS_UNREAD:
616 case MATCHING_ACTION_FORWARD:
617 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
618 return FALSE; /* MsgInfo still valid for message */
627 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
628 GHashTable *folder_table)
633 g_warning(_("msginfo is not set"));
637 for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
638 FilteringProp * filtering = (FilteringProp *) l->data;
640 if (filteringprop_apply(filtering, info, folder_table))
645 void filter_msginfo_move_or_delete(GSList * filtering_list, MsgInfo * info,
646 GHashTable *folder_table)
651 g_warning(_("msginfo is not set"));
655 for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
656 FilteringProp * filtering = (FilteringProp *) l->data;
658 switch (filtering->action->type) {
659 case MATCHING_ACTION_MOVE:
660 case MATCHING_ACTION_DELETE:
661 if (filteringprop_apply(filtering, info, folder_table))
667 void filter_message(GSList * filtering_list, FolderItem * item,
668 gint msgnum, GHashTable *folder_table)
674 g_warning(_("folderitem not set"));
678 filename = folder_item_fetch_msg(item, msgnum);
680 if (filename == NULL) {
681 g_warning(_("filename is not set"));
685 msginfo = procheader_parse(filename, 0, TRUE);
689 if (msginfo == NULL) {
690 g_warning(_("could not get info for %s"), filename);
694 msginfo->folder = item;
695 msginfo->msgnum = msgnum;
697 filter_msginfo(filtering_list, msginfo, folder_table);
700 void prefs_filtering_read_config(void)
704 gchar buf[PREFSBUFSIZE];
706 debug_print(_("Reading filtering configuration...\n"));
708 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
710 if ((fp = fopen(rcpath, "r")) == NULL) {
711 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
713 prefs_filtering = NULL;
718 /* remove all filtering */
719 while (prefs_filtering != NULL) {
720 FilteringProp * filtering =
721 (FilteringProp *) prefs_filtering->data;
722 filteringprop_free(filtering);
723 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
726 while (fgets(buf, sizeof(buf), fp) != NULL) {
727 FilteringProp * filtering;
732 if ((*buf != '#') && (*buf != '\0')) {
734 filtering = filteringprop_parse(&tmp);
737 g_slist_append(prefs_filtering,
742 g_warning(_("syntax error : %s\n"), buf);
750 gchar * filteringaction_to_string(FilteringAction * action)
754 gchar * account_id_str;
757 command_str = get_matchparser_tab_str(action->type);
759 if (command_str == NULL)
762 switch(action->type) {
763 case MATCHING_ACTION_MOVE:
764 case MATCHING_ACTION_COPY:
765 case MATCHING_EXECUTE:
766 return g_strconcat(command_str, " \"", action->destination,
769 case MATCHING_ACTION_DELETE:
770 case MATCHING_ACTION_MARK:
771 case MATCHING_ACTION_UNMARK:
772 case MATCHING_ACTION_MARK_AS_READ:
773 case MATCHING_ACTION_MARK_AS_UNREAD:
774 return g_strdup(command_str);
777 case MATCHING_ACTION_FORWARD:
778 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
779 account_id_str = itos(action->account_id);
780 return g_strconcat(command_str, " ", account_id_str,
781 " \"", action->destination, "\"", NULL);
788 gchar * filteringprop_to_string(FilteringProp * prop)
792 gchar * filtering_str;
794 action_str = filteringaction_to_string(prop->action);
796 if (action_str == NULL)
799 list_str = matcherlist_to_string(prop->matchers);
801 if (list_str == NULL) {
806 filtering_str = g_strconcat(list_str, " ", action_str, NULL);
810 return filtering_str;
813 void prefs_filtering_write_config(void)
818 FilteringProp * prop;
820 debug_print(_("Writing filtering configuration...\n"));
822 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
824 if ((pfile = prefs_write_open(rcpath)) == NULL) {
825 g_warning(_("failed to write configuration to file\n"));
830 for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
831 gchar *filtering_str;
833 prop = (FilteringProp *) cur->data;
834 filtering_str = filteringprop_to_string(prop);
835 if (fputs(filtering_str, pfile->fp) == EOF ||
836 fputc('\n', pfile->fp) == EOF) {
837 FILE_OP_ERROR(rcpath, "fputs || fputc");
838 prefs_write_close_revert(pfile);
840 g_free(filtering_str);
843 g_free(filtering_str);
848 if (prefs_write_close(pfile) < 0) {
849 g_warning(_("failed to write configuration to file\n"));