From 3bf24b9336184fe9e28f7e09b9c5200a5f82b7d2 Mon Sep 17 00:00:00 2001 From: hiro Date: Mon, 5 Sep 2005 10:00:53 +0000 Subject: moved more modules to libsylph. git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@548 ee746299-78ed-0310-b773-934348b2243d --- libsylph/filter.c | 1421 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1421 insertions(+) create mode 100644 libsylph/filter.c (limited to 'libsylph/filter.c') diff --git a/libsylph/filter.c b/libsylph/filter.c new file mode 100644 index 00000000..efc2fa02 --- /dev/null +++ b/libsylph/filter.c @@ -0,0 +1,1421 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#if HAVE_REGEX_H +# include +#endif +#include + +#include "filter.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "utils.h" +#include "xml.h" +#include "prefs.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "account.h" + +typedef enum +{ + FLT_O_CONTAIN = 1 << 0, + FLT_O_CASE_SENS = 1 << 1, + FLT_O_REGEX = 1 << 2 +} FilterOldFlag; + +static gboolean filter_match_cond (FilterCond *cond, + MsgInfo *msginfo, + GSList *hlist, + FilterInfo *fltinfo); +static gboolean filter_match_header_cond(FilterCond *cond, + GSList *hlist); + +static void filter_cond_free (FilterCond *cond); +static void filter_action_free (FilterAction *action); + + +gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo) +{ + MsgInfo *msginfo; + gint ret = 0; + + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + if (!fltlist) return 0; + + msginfo = procheader_parse_file(file, fltinfo->flags, FALSE); + if (!msginfo) return 0; + msginfo->file_path = g_strdup(file); + + ret = filter_apply_msginfo(fltlist, msginfo, fltinfo); + + procmsg_msginfo_free(msginfo); + + return ret; +} + +gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo, + FilterInfo *fltinfo) +{ + gchar *file; + GSList *hlist, *cur; + FilterRule *rule; + gint ret = 0; + + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + if (!fltlist) return 0; + + file = procmsg_get_message_file(msginfo); + hlist = procheader_get_header_list_from_file(file); + if (!hlist) { + g_free(file); + return 0; + } + + for (cur = fltlist; cur != NULL; cur = cur->next) { + rule = (FilterRule *)cur->data; + if (!rule->enabled) continue; + if (filter_match_rule(rule, msginfo, hlist, fltinfo)) { + ret = filter_action_exec(rule, msginfo, file, fltinfo); + if (ret < 0) { + g_warning("filter_action_exec() returned error\n"); + break; + } + if (fltinfo->drop_done == TRUE || + fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE) + break; + } + } + + procheader_header_list_destroy(hlist); + g_free(file); + + return ret; +} + +gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file, + FilterInfo *fltinfo) +{ + FolderItem *dest_folder = NULL; + FilterAction *action; + GSList *cur; + gchar *cmdline; + gboolean copy_to_self = FALSE; + + g_return_val_if_fail(rule != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_MARK: + debug_print("filter_action_exec(): mark\n"); + MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_COLOR_LABEL: + debug_print("filter_action_exec(): color label: %d\n", + action->int_value); + MSG_UNSET_PERM_FLAGS(fltinfo->flags, + MSG_CLABEL_FLAG_MASK); + MSG_SET_COLORLABEL_VALUE(fltinfo->flags, + action->int_value); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_MARK_READ: + debug_print("filter_action_exec(): mark as read\n"); + if (msginfo->folder) { + if (MSG_IS_NEW(fltinfo->flags)) + msginfo->folder->new--; + if (MSG_IS_UNREAD(fltinfo->flags)) + msginfo->folder->unread--; + } + MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_EXEC: + cmdline = g_strconcat(action->str_value, " ", file, + NULL); + execute_command_line(cmdline, FALSE); + g_free(cmdline); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_EXEC_ASYNC: + cmdline = g_strconcat(action->str_value, " ", file, + NULL); + execute_command_line(cmdline, TRUE); + g_free(cmdline); + fltinfo->actions[action->type] = TRUE; + break; + default: + break; + } + } + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_MOVE: + case FLT_ACTION_COPY: + dest_folder = folder_find_item_from_identifier + (action->str_value); + if (!dest_folder) { + g_warning("dest folder '%s' not found\n", + action->str_value); + return -1; + } + debug_print("filter_action_exec(): %s: dest_folder = %s\n", + action->type == FLT_ACTION_COPY ? + "copy" : "move", action->str_value); + + if (msginfo->folder) { + gint val; + + /* local filtering */ + if (msginfo->folder == dest_folder) + copy_to_self = TRUE; + else { + if (action->type == FLT_ACTION_COPY) { + val = folder_item_copy_msg + (dest_folder, msginfo); + if (val == -1) + return -1; + } + fltinfo->actions[action->type] = TRUE; + } + } else { + if (folder_item_add_msg(dest_folder, file, + &fltinfo->flags, + FALSE) < 0) + return -1; + fltinfo->actions[action->type] = TRUE; + } + + fltinfo->dest_list = g_slist_append(fltinfo->dest_list, + dest_folder); + if (action->type == FLT_ACTION_MOVE) { + fltinfo->move_dest = dest_folder; + fltinfo->drop_done = TRUE; + } + break; + default: + break; + } + } + + if (fltinfo->drop_done == TRUE) + return 0; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_NOT_RECEIVE: + debug_print("filter_action_exec(): don't receive\n"); + fltinfo->drop_done = TRUE; + fltinfo->actions[action->type] = TRUE; + return 0; + case FLT_ACTION_DELETE: + debug_print("filter_action_exec(): delete\n"); + if (msginfo->folder) { + /* local filtering */ + if (copy_to_self == FALSE) + fltinfo->actions[action->type] = TRUE; + } else + fltinfo->actions[action->type] = TRUE; + + fltinfo->drop_done = TRUE; + return 0; + case FLT_ACTION_STOP_EVAL: + debug_print("filter_action_exec(): stop evaluation\n"); + fltinfo->actions[action->type] = TRUE; + return 0; + default: + break; + } + } + + return 0; +} + +static gboolean strmatch_regex(const gchar *haystack, const gchar *needle) +{ +#if HAVE_REGEX_H && HAVE_REGCOMP + gint ret = 0; + regex_t preg; + regmatch_t pmatch[1]; + + ret = regcomp(&preg, needle, REG_EXTENDED|REG_ICASE); + if (ret != 0) return FALSE; + + ret = regexec(&preg, haystack, 1, pmatch, 0); + regfree(&preg); + + if (ret == REG_NOMATCH) return FALSE; + + if (pmatch[0].rm_so != -1) + return TRUE; + else +#endif + return FALSE; +} + +gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist, + FilterInfo *fltinfo) +{ + FilterCond *cond; + GSList *cur; + gboolean matched; + + g_return_val_if_fail(rule->cond_list != NULL, FALSE); + g_return_val_if_fail(rule->action_list != NULL, FALSE); + + switch (rule->timing) { + case FLT_TIMING_ANY: + break; + case FLT_TIMING_ON_RECEIVE: + if (msginfo->folder != NULL) + return FALSE; + break; + case FLT_TIMING_MANUAL: + if (msginfo->folder == NULL) + return FALSE; + break; + default: + break; + } + + if (rule->bool_op == FLT_AND) { + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + matched = filter_match_cond(cond, msginfo, hlist, + fltinfo); + if (matched == FALSE) + return FALSE; + } + + return TRUE; + } else if (rule->bool_op == FLT_OR) { + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + matched = filter_match_cond(cond, msginfo, hlist, + fltinfo); + if (matched == TRUE) + return TRUE; + } + + return FALSE; + } + + return FALSE; +} + +static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo, + GSList *hlist, FilterInfo *fltinfo) +{ + gboolean matched = FALSE; + gchar *file; + gchar *cmdline; + PrefsAccount *cond_ac; + + switch (cond->type) { + case FLT_COND_HEADER: + case FLT_COND_ANY_HEADER: + case FLT_COND_TO_OR_CC: + return filter_match_header_cond(cond, hlist); + case FLT_COND_BODY: + matched = procmime_find_string(msginfo, cond->str_value, + cond->match_func); + break; + case FLT_COND_CMD_TEST: + file = procmsg_get_message_file(msginfo); + cmdline = g_strconcat(cond->str_value, " ", file, NULL); + matched = (execute_command_line(cmdline, FALSE) == 0); + g_free(cmdline); + g_free(file); + break; + case FLT_COND_SIZE_GREATER: + matched = (msginfo->size > cond->int_value * 1024); + break; + case FLT_COND_AGE_GREATER: + matched = (time(NULL) - msginfo->date_t > + cond->int_value * 24 * 60 * 60); + break; + case FLT_COND_ACCOUNT: + cond_ac = account_find_from_id(cond->int_value); + matched = (cond_ac != NULL && cond_ac == fltinfo->account); + break; + default: + g_warning("filter_match_cond(): unknown condition: %d\n", + cond->type); + return FALSE; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) + matched = !matched; + + return matched; +} + +static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist) +{ + gboolean matched = FALSE; + GSList *cur; + Header *header; + + for (cur = hlist; cur != NULL; cur = cur->next) { + header = (Header *)cur->data; + + switch (cond->type) { + case FLT_COND_HEADER: + if (!g_ascii_strcasecmp + (header->name, cond->header_name)) { + if (!cond->str_value || + cond->match_func(header->body, + cond->str_value)) + matched = TRUE; + } + break; + case FLT_COND_ANY_HEADER: + if (!cond->str_value || + cond->match_func(header->body, cond->str_value)) + matched = TRUE; + break; + case FLT_COND_TO_OR_CC: + if (!g_ascii_strcasecmp(header->name, "To") || + !g_ascii_strcasecmp(header->name, "Cc")) { + if (!cond->str_value || + cond->match_func(header->body, + cond->str_value)) + matched = TRUE; + } + break; + default: + break; + } + + if (matched == TRUE) + break; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) + matched = !matched; + + return matched; +} + +#define RETURN_IF_TAG_NOT_MATCH(tag_name) \ + if (strcmp2(xmlnode->tag->tag, tag_name) != 0) { \ + g_warning("tag name != \"" tag_name "\"\n"); \ + filter_cond_list_free(cond_list); \ + filter_action_list_free(cond_list); \ + return FALSE; \ + } \ + +#define STR_SWITCH(sw) { const gchar *sw_str = sw; +#define STR_CASE_BEGIN(str) if (!strcmp(sw_str, str)) { +#define STR_CASE(str) } else if (!strcmp(sw_str, str)) { +#define STR_CASE_END } } + +static gboolean filter_xml_node_func(GNode *node, gpointer data) +{ + XMLNode *xmlnode; + GList *list; + const gchar *name = NULL; + FilterTiming timing = FLT_TIMING_ANY; + gboolean enabled = TRUE; + FilterRule *rule; + FilterBoolOp bool_op = FLT_OR; + GSList *cond_list = NULL; + GSList *action_list = NULL; + GNode *child, *cond_child, *action_child; + GSList **fltlist = (GSList **)data; + + if (g_node_depth(node) != 2) return FALSE; + g_return_val_if_fail(node->data != NULL, FALSE); + + xmlnode = node->data; + RETURN_IF_TAG_NOT_MATCH("rule"); + + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "timing")) { + if (!strcmp(attr->value, "any")) + timing = FLT_TIMING_ANY; + else if (!strcmp(attr->value, "receive")) + timing = FLT_TIMING_ON_RECEIVE; + else if (!strcmp(attr->value, "manual")) + timing = FLT_TIMING_MANUAL; + } else if (!strcmp(attr->name, "enabled")) { + if (!strcmp(attr->value, "true")) + enabled = TRUE; + else + enabled = FALSE; + } + } + + /* condition list */ + child = node->children; + if (!child) { + g_warning(" must have children\n"); + return FALSE; + } + xmlnode = child->data; + RETURN_IF_TAG_NOT_MATCH("condition-list"); + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (attr && attr->name && attr->value && + !strcmp(attr->name, "bool")) { + if (!strcmp(attr->value, "or")) + bool_op = FLT_OR; + else + bool_op = FLT_AND; + } + } + + for (cond_child = child->children; cond_child != NULL; + cond_child = cond_child->next) { + const gchar *type = NULL; + const gchar *name = NULL; + const gchar *value = NULL; + FilterCond *cond; + FilterCondType cond_type = FLT_COND_HEADER; + FilterMatchType match_type = FLT_CONTAIN; + FilterMatchFlag match_flag = 0; + + xmlnode = cond_child->data; + if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue; + + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "type")) + type = attr->value; + else if (!strcmp(attr->name, "name")) + name = attr->value; + } + + if (type) { + filter_rule_match_type_str_to_enum + (type, &match_type, &match_flag); + } + value = xmlnode->element; + + STR_SWITCH(xmlnode->tag->tag) + STR_CASE_BEGIN("match-header") + cond_type = FLT_COND_HEADER; + STR_CASE("match-any-header") + cond_type = FLT_COND_ANY_HEADER; + STR_CASE("match-to-or-cc") + cond_type = FLT_COND_TO_OR_CC; + STR_CASE("match-body-text") + cond_type = FLT_COND_BODY; + STR_CASE("command-test") + cond_type = FLT_COND_CMD_TEST; + STR_CASE("size") + cond_type = FLT_COND_SIZE_GREATER; + STR_CASE("age") + cond_type = FLT_COND_AGE_GREATER; + STR_CASE("account-id") + cond_type = FLT_COND_ACCOUNT; + STR_CASE_END + + cond = filter_cond_new(cond_type, match_type, match_flag, + name, value); + cond_list = g_slist_append(cond_list, cond); + } + + /* action list */ + child = child->next; + if (!child) { + g_warning(" must have multiple children\n"); + filter_cond_list_free(cond_list); + return FALSE; + } + xmlnode = child->data; + RETURN_IF_TAG_NOT_MATCH("action-list"); + + for (action_child = child->children; action_child != NULL; + action_child = action_child->next) { + FilterAction *action; + FilterActionType action_type = FLT_ACTION_NONE; + + xmlnode = action_child->data; + if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue; + + STR_SWITCH(xmlnode->tag->tag) + STR_CASE_BEGIN("move") + action_type = FLT_ACTION_MOVE; + STR_CASE("copy") + action_type = FLT_ACTION_COPY; + STR_CASE("not-receive") + action_type = FLT_ACTION_NOT_RECEIVE; + STR_CASE("delete") + action_type = FLT_ACTION_DELETE; + STR_CASE("exec") + action_type = FLT_ACTION_EXEC; + STR_CASE("exec-async") + action_type = FLT_ACTION_EXEC_ASYNC; + STR_CASE("mark") + action_type = FLT_ACTION_MARK; + STR_CASE("color-label") + action_type = FLT_ACTION_COLOR_LABEL; + STR_CASE("mark-as-read") + action_type = FLT_ACTION_MARK_READ; + STR_CASE("forward") + action_type = FLT_ACTION_FORWARD; + STR_CASE("forward-as-attachment") + action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT; + STR_CASE("redirect") + action_type = FLT_ACTION_REDIRECT; + STR_CASE("stop-eval") + action_type = FLT_ACTION_STOP_EVAL; + STR_CASE_END + + action = filter_action_new(action_type, xmlnode->element); + action_list = g_slist_append(action_list, action); + } + + if (name && cond_list && action_list) { + rule = filter_rule_new(name, bool_op, cond_list, action_list); + rule->timing = timing; + rule->enabled = enabled; + *fltlist = g_slist_prepend(*fltlist, rule); + } + + return FALSE; +} + +#undef RETURN_IF_TAG_NOT_MATCH +#undef STR_SWITCH +#undef STR_CASE_BEGIN +#undef STR_CASE +#undef STR_CASE_END + +GSList *filter_xml_node_to_filter_list(GNode *node) +{ + GSList *fltlist = NULL; + + g_return_val_if_fail(node != NULL, NULL); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2, + filter_xml_node_func, &fltlist); + fltlist = g_slist_reverse(fltlist); + + return fltlist; +} + +void filter_read_config(void) +{ + gchar *rcpath; + GNode *node; + FilterRule *rule; + + debug_print("Reading filter configuration...\n"); + + /* remove all previous filter list */ + while (prefs_common.fltlist != NULL) { + rule = (FilterRule *)prefs_common.fltlist->data; + filter_rule_free(rule); + prefs_common.fltlist = g_slist_remove(prefs_common.fltlist, + rule); + } + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST, + NULL); + if (!is_file_exist(rcpath)) { + g_free(rcpath); + return; + } + + node = xml_parse_file(rcpath); + if (!node) { + g_warning("Can't parse %s\n", rcpath); + g_free(rcpath); + return; + } + g_free(rcpath); + + prefs_common.fltlist = filter_xml_node_to_filter_list(node); + + xml_free_tree(node); +} + +#define NODE_NEW(tag, text) \ + node = xml_node_new(xml_tag_new(tag), text) +#define ADD_ATTR(name, value) \ + xml_tag_add_attr(node->tag, xml_attr_new(name, value)) + +void filter_write_config(void) +{ + gchar *rcpath; + PrefFile *pfile; + GSList *cur; + + debug_print("Writing filter configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST, + NULL); + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning("failed to write filter configuration to file\n"); + g_free(rcpath); + return; + } + + xml_file_put_xml_decl(pfile->fp); + fputs("\n\n", pfile->fp); + + for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + GSList *cur_cond; + GSList *cur_action; + gchar match_type[16]; + + fputs(" fp); + xml_file_put_escape_str(pfile->fp, rule->name); + fprintf(pfile->fp, "\" timing=\"%s\"", + rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" : + rule->timing == FLT_TIMING_MANUAL ? "manual" : "any"); + fprintf(pfile->fp, " enabled=\"%s\">\n", + rule->enabled ? "true" : "false"); + + fprintf(pfile->fp, " \n", + rule->bool_op == FLT_OR ? "or" : "and"); + + for (cur_cond = rule->cond_list; cur_cond != NULL; + cur_cond = cur_cond->next) { + FilterCond *cond = (FilterCond *)cur_cond->data; + XMLNode *node = NULL; + + switch (cond->match_type) { + case FLT_CONTAIN: + strcpy(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-contain" : "contains"); + break; + case FLT_EQUAL: + strcpy(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is"); + break; + case FLT_REGEX: + strcpy(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-regex" : "regex"); + break; + default: + match_type[0] = '\0'; + break; + } + + switch (cond->type) { + case FLT_COND_HEADER: + NODE_NEW("match-header", cond->str_value); + ADD_ATTR("type", match_type); + ADD_ATTR("name", cond->header_name); + break; + case FLT_COND_ANY_HEADER: + NODE_NEW("match-any-header", cond->str_value); + ADD_ATTR("type", match_type); + break; + case FLT_COND_TO_OR_CC: + NODE_NEW("match-to-or-cc", cond->str_value); + ADD_ATTR("type", match_type); + break; + case FLT_COND_BODY: + NODE_NEW("match-body-text", cond->str_value); + ADD_ATTR("type", match_type); + break; + case FLT_COND_CMD_TEST: + NODE_NEW("command-test", cond->str_value); + break; + case FLT_COND_SIZE_GREATER: + NODE_NEW("size", itos(cond->int_value)); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "lt" : "gt"); + break; + case FLT_COND_AGE_GREATER: + NODE_NEW("age", itos(cond->int_value)); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "lt" : "gt"); + break; + case FLT_COND_ACCOUNT: + NODE_NEW("account-id", itos(cond->int_value)); + break; + default: + break; + } + + if (node) { + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + } + + fputs(" \n", pfile->fp); + + fputs(" \n", pfile->fp); + + for (cur_action = rule->action_list; cur_action != NULL; + cur_action = cur_action->next) { + FilterAction *action = (FilterAction *)cur_action->data; + XMLNode *node = NULL; + + switch (action->type) { + case FLT_ACTION_MOVE: + NODE_NEW("move", action->str_value); + break; + case FLT_ACTION_COPY: + NODE_NEW("copy", action->str_value); + break; + case FLT_ACTION_NOT_RECEIVE: + NODE_NEW("not-receive", NULL); + break; + case FLT_ACTION_DELETE: + NODE_NEW("delete", NULL); + break; + case FLT_ACTION_EXEC: + NODE_NEW("exec", action->str_value); + break; + case FLT_ACTION_EXEC_ASYNC: + NODE_NEW("exec-async", action->str_value); + break; + case FLT_ACTION_MARK: + NODE_NEW("mark", NULL); + break; + case FLT_ACTION_COLOR_LABEL: + NODE_NEW("color-label", action->str_value); + break; + case FLT_ACTION_MARK_READ: + NODE_NEW("mark-as-read", NULL); + break; + case FLT_ACTION_FORWARD: + NODE_NEW("forward", action->str_value); + break; + case FLT_ACTION_FORWARD_AS_ATTACHMENT: + NODE_NEW("forward-as-attachment", + action->str_value); + break; + case FLT_ACTION_REDIRECT: + NODE_NEW("redirect", action->str_value); + break; + case FLT_ACTION_STOP_EVAL: + NODE_NEW("stop-eval", NULL); + break; + default: + break; + } + + if (node) { + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + } + + fputs(" \n", pfile->fp); + + fputs(" \n", pfile->fp); + } + + fputs("\n", pfile->fp); + + g_free(rcpath); + + if (prefs_file_close(pfile) < 0) { + g_warning(_("failed to write configuration to file\n")); + return; + } +} + +#undef NODE_NEW +#undef ADD_ATTR + +gchar *filter_get_str(FilterRule *rule) +{ + gchar *str; + FilterCond *cond1, *cond2; + FilterAction *action = NULL; + GSList *cur; + gint flag1 = 0, flag2 = 0; + + cond1 = (FilterCond *)rule->cond_list->data; + if (rule->cond_list->next) + cond2 = (FilterCond *)rule->cond_list->next->data; + else + cond2 = NULL; + + /* new -> old flag conversion */ + switch (cond1->match_type) { + case FLT_CONTAIN: + case FLT_EQUAL: + flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN; + if (FLT_IS_CASE_SENS(cond1->match_flag)) + flag1 |= FLT_O_CASE_SENS; + break; + case FLT_REGEX: + flag1 = FLT_O_REGEX; break; + default: + break; + } + if (cond2) { + switch (cond2->match_type) { + case FLT_CONTAIN: + case FLT_EQUAL: + flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN; + if (FLT_IS_CASE_SENS(cond2->match_flag)) + flag2 |= FLT_O_CASE_SENS; + break; + case FLT_REGEX: + flag2 = FLT_O_REGEX; break; + default: + break; + } + } else + flag2 = FLT_O_CONTAIN; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + if (action->type == FLT_ACTION_MOVE || + action->type == FLT_ACTION_NOT_RECEIVE || + action->type == FLT_ACTION_DELETE) + break; + } + + str = g_strdup_printf + ("%s:%s:%c:%s:%s:%s:%d:%d:%c", + cond1->header_name, cond1->str_value ? cond1->str_value : "", + (cond2 && cond2->header_name) ? + (rule->bool_op == FLT_AND ? '&' : '|') : ' ', + (cond2 && cond2->header_name) ? cond2->header_name : "", + (cond2 && cond2->str_value) ? cond2->str_value : "", + (action && action->str_value) ? action->str_value : "", + flag1, flag2, + (action && action->type == FLT_ACTION_MOVE) ? 'm' : + (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' : + (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' '); + + return str; +} + +#define PARSE_ONE_PARAM(p, srcp) \ +{ \ + p = strchr(srcp, '\t'); \ + if (!p) return NULL; \ + else \ + *p++ = '\0'; \ +} + +FilterRule *filter_read_str(const gchar *str) +{ + FilterRule *rule; + FilterBoolOp bool_op; + gint flag; + FilterCond *cond; + FilterMatchType match_type; + FilterMatchFlag match_flag; + FilterAction *action; + GSList *cond_list = NULL; + GSList *action_list = NULL; + gchar *tmp; + gchar *rule_name; + gchar *name1, *body1, *op, *name2, *body2, *dest; + gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL; + + Xstrdup_a(tmp, str, return NULL); + + name1 = tmp; + PARSE_ONE_PARAM(body1, name1); + PARSE_ONE_PARAM(op, body1); + PARSE_ONE_PARAM(name2, op); + PARSE_ONE_PARAM(body2, name2); + PARSE_ONE_PARAM(dest, body2); + if (strchr(dest, '\t')) { + gchar *p; + + PARSE_ONE_PARAM(flag1, dest); + PARSE_ONE_PARAM(flag2, flag1); + PARSE_ONE_PARAM(action1, flag2); + if ((p = strchr(action1, '\t'))) *p = '\0'; + } + + bool_op = (*op == '&') ? FLT_AND : FLT_OR; + + if (*name1) { + if (flag1) + flag = (FilterOldFlag)strtoul(flag1, NULL, 10); + else + flag = FLT_O_CONTAIN; + match_type = FLT_CONTAIN; + match_flag = 0; + if (flag & FLT_O_REGEX) + match_type = FLT_REGEX; + else if (!(flag & FLT_O_CONTAIN)) + match_flag = FLT_NOT_MATCH; + if (flag & FLT_O_CASE_SENS) + match_flag |= FLT_CASE_SENS; + cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag, + name1, body1); + cond_list = g_slist_append(cond_list, cond); + } + if (*name2) { + if (flag2) + flag = (FilterOldFlag)strtoul(flag2, NULL, 10); + else + flag = FLT_O_CONTAIN; + match_type = FLT_CONTAIN; + match_flag = 0; + if (flag & FLT_O_REGEX) + match_type = FLT_REGEX; + else if (!(flag & FLT_O_CONTAIN)) + match_flag = FLT_NOT_MATCH; + if (flag & FLT_O_CASE_SENS) + match_flag |= FLT_CASE_SENS; + cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag, + name2, body2); + cond_list = g_slist_append(cond_list, cond); + } + + action = filter_action_new(FLT_ACTION_MOVE, + *dest ? g_strdup(dest) : NULL); + if (action1) { + switch (*action1) { + case 'm': action->type = FLT_ACTION_MOVE; break; + case 'n': action->type = FLT_ACTION_NOT_RECEIVE; break; + case 'd': action->type = FLT_ACTION_DELETE; break; + default: g_warning("Invalid action: `%c'\n", *action1); + } + } + action_list = g_slist_append(action_list, action); + + Xstrdup_a(rule_name, str, return NULL); + subst_char(rule_name, '\t', ':'); + + rule = filter_rule_new(rule_name, bool_op, cond_list, action_list); + + return rule; +} + +FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op, + GSList *cond_list, GSList *action_list) +{ + FilterRule *rule; + + rule = g_new0(FilterRule, 1); + rule->name = g_strdup(name); + rule->bool_op = bool_op; + rule->cond_list = cond_list; + rule->action_list = action_list; + rule->timing = FLT_TIMING_ANY; + rule->enabled = TRUE; + + return rule; +} + +FilterCond *filter_cond_new(FilterCondType type, + FilterMatchType match_type, + FilterMatchFlag match_flag, + const gchar *header, const gchar *value) +{ + FilterCond *cond; + + cond = g_new0(FilterCond, 1); + cond->type = type; + cond->match_type = match_type; + cond->match_flag = match_flag; + + if (type == FLT_COND_HEADER) + cond->header_name = + (header && *header) ? g_strdup(header) : NULL; + else + cond->header_name = NULL; + + cond->str_value = (value && *value) ? g_strdup(value) : NULL; + if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER) + cond->int_value = atoi(value); + else + cond->int_value = 0; + + if (match_type == FLT_REGEX) + cond->match_func = strmatch_regex; + else if (match_type == FLT_EQUAL) { + if (FLT_IS_CASE_SENS(match_flag)) + cond->match_func = str_find_equal; + else + cond->match_func = str_case_find_equal; + } else { + if (FLT_IS_CASE_SENS(match_flag)) + cond->match_func = str_find; + else + cond->match_func = str_case_find; + } + + return cond; +} + +FilterAction *filter_action_new(FilterActionType type, const gchar *str) +{ + FilterAction *action; + + action = g_new0(FilterAction, 1); + action->type = type; + + action->str_value = (str && *str) ? g_strdup(str) : NULL; + if (type == FLT_ACTION_COLOR_LABEL && str) + action->int_value = atoi(str); + else + action->int_value = 0; + + return action; +} + +FilterInfo *filter_info_new(void) +{ + FilterInfo *fltinfo; + + fltinfo = g_new0(FilterInfo, 1); + fltinfo->dest_list = NULL; + fltinfo->move_dest = NULL; + fltinfo->drop_done = FALSE; + + return fltinfo; +} + +void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path, + const gchar *new_path) +{ + FilterAction *action; + GSList *cur; + gchar *base; + gchar *dest_path; + gint oldpathlen; + + oldpathlen = strlen(old_path); + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + if (action->type != FLT_ACTION_MOVE && + action->type != FLT_ACTION_COPY) + continue; + + if (action->str_value && + !strncmp(old_path, action->str_value, oldpathlen)) { + base = action->str_value + oldpathlen; + if (*base != G_DIR_SEPARATOR && *base != '\0') + continue; + while (*base == G_DIR_SEPARATOR) base++; + if (*base == '\0') + dest_path = g_strdup(new_path); + else + dest_path = g_strconcat(new_path, + G_DIR_SEPARATOR_S, + base, NULL); + debug_print("filter_rule_rename_dest_path(): " + "renaming %s -> %s\n", + action->str_value, dest_path); + g_free(action->str_value); + action->str_value = dest_path; + } + } +} + +void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path) +{ + FilterAction *action; + GSList *cur; + GSList *next; + gint pathlen; + + pathlen = strlen(path); + + for (cur = rule->action_list; cur != NULL; cur = next) { + action = (FilterAction *)cur->data; + next = cur->next; + + if (action->type != FLT_ACTION_MOVE && + action->type != FLT_ACTION_COPY) + continue; + + if (action->str_value && + !strncmp(path, action->str_value, pathlen) && + (action->str_value[pathlen] == G_DIR_SEPARATOR || + action->str_value[pathlen] == '\0')) { + debug_print("filter_rule_delete_action_by_dest_path(): " + "deleting %s\n", action->str_value); + rule->action_list = g_slist_remove + (rule->action_list, action); + filter_action_free(action); + } + } +} + +void filter_list_rename_path(const gchar *old_path, const gchar *new_path) +{ + GSList *cur; + + g_return_if_fail(old_path != NULL); + g_return_if_fail(new_path != NULL); + + for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + filter_rule_rename_dest_path(rule, old_path, new_path); + } + + filter_write_config(); +} + +void filter_list_delete_path(const gchar *path) +{ + GSList *cur; + GSList *next; + + g_return_if_fail(path != NULL); + + for (cur = prefs_common.fltlist; cur != NULL; cur = next) { + FilterRule *rule = (FilterRule *)cur->data; + next = cur->next; + + filter_rule_delete_action_by_dest_path(rule, path); + if (!rule->action_list) { + prefs_common.fltlist = + g_slist_remove(prefs_common.fltlist, rule); + filter_rule_free(rule); + } + } + + filter_write_config(); +} + +void filter_rule_match_type_str_to_enum(const gchar *match_type, + FilterMatchType *type, + FilterMatchFlag *flag) +{ + g_return_if_fail(match_type != NULL); + + *type = FLT_CONTAIN; + *flag = 0; + + if (!strcmp(match_type, "contains")) { + *type = FLT_CONTAIN; + } else if (!strcmp(match_type, "not-contain")) { + *type = FLT_CONTAIN; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "is")) { + *type = FLT_EQUAL; + } else if (!strcmp(match_type, "is-not")) { + *type = FLT_EQUAL; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "regex")) { + *type = FLT_REGEX; + } else if (!strcmp(match_type, "not-regex")) { + *type = FLT_REGEX; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "gt")) { + } else if (!strcmp(match_type, "lt")) { + *flag = FLT_NOT_MATCH; + } +} + +void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key, + FilterCreateType type) +{ + static HeaderEntry hentry[] = {{"List-Id:", NULL, TRUE}, + {"X-ML-Name:", NULL, TRUE}, + {"X-List:", NULL, TRUE}, + {"X-Mailing-list:", NULL, TRUE}, + {"X-Sequence:", NULL, TRUE}, + {NULL, NULL, FALSE}}; + enum + { + H_LIST_ID = 0, + H_X_ML_NAME = 1, + H_X_LIST = 2, + H_X_MAILING_LIST = 3, + H_X_SEQUENCE = 4 + }; + + FILE *fp; + + g_return_if_fail(msginfo != NULL); + g_return_if_fail(header != NULL); + g_return_if_fail(key != NULL); + + *header = NULL; + *key = NULL; + + switch (type) { + case FLT_BY_NONE: + return; + case FLT_BY_AUTO: + if ((fp = procmsg_open_message(msginfo)) == NULL) + return; + procheader_get_header_fields(fp, hentry); + fclose(fp); + +#define SET_FILTER_KEY(hstr, idx) \ +{ \ + *header = g_strdup(hstr); \ + *key = hentry[idx].body; \ + hentry[idx].body = NULL; \ +} + + if (hentry[H_LIST_ID].body != NULL) { + SET_FILTER_KEY("List-Id", H_LIST_ID); + extract_list_id_str(*key); + } else if (hentry[H_X_ML_NAME].body != NULL) { + SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME); + } else if (hentry[H_X_LIST].body != NULL) { + SET_FILTER_KEY("X-List", H_X_LIST); + } else if (hentry[H_X_MAILING_LIST].body != NULL) { + SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST); + } else if (hentry[H_X_SEQUENCE].body != NULL) { + gchar *p; + + SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE); + p = *key; + while (*p != '\0') { + while (*p != '\0' && !g_ascii_isspace(*p)) p++; + while (g_ascii_isspace(*p)) p++; + if (g_ascii_isdigit(*p)) { + *p = '\0'; + break; + } + } + g_strstrip(*key); + } else if (msginfo->subject) { + *header = g_strdup("Subject"); + *key = g_strdup(msginfo->subject); + } + +#undef SET_FILTER_KEY + + g_free(hentry[H_LIST_ID].body); + hentry[H_LIST_ID].body = NULL; + g_free(hentry[H_X_ML_NAME].body); + hentry[H_X_ML_NAME].body = NULL; + g_free(hentry[H_X_LIST].body); + hentry[H_X_LIST].body = NULL; + g_free(hentry[H_X_MAILING_LIST].body); + hentry[H_X_MAILING_LIST].body = NULL; + + break; + case FLT_BY_FROM: + *header = g_strdup("From"); + *key = g_strdup(msginfo->from); + break; + case FLT_BY_TO: + *header = g_strdup("To"); + *key = g_strdup(msginfo->to); + break; + case FLT_BY_SUBJECT: + *header = g_strdup("Subject"); + *key = g_strdup(msginfo->subject); + break; + default: + break; + } +} + +void filter_rule_list_free(GSList *fltlist) +{ + GSList *cur; + + for (cur = fltlist; cur != NULL; cur = cur->next) + filter_rule_free((FilterRule *)cur->data); + g_slist_free(fltlist); +} + +void filter_rule_free(FilterRule *rule) +{ + if (!rule) return; + + g_free(rule->name); + + filter_cond_list_free(rule->cond_list); + filter_action_list_free(rule->action_list); + + g_free(rule); +} + +void filter_cond_list_free(GSList *cond_list) +{ + GSList *cur; + + for (cur = cond_list; cur != NULL; cur = cur->next) + filter_cond_free((FilterCond *)cur->data); + g_slist_free(cond_list); +} + +void filter_action_list_free(GSList *action_list) +{ + GSList *cur; + + for (cur = action_list; cur != NULL; cur = cur->next) + filter_action_free((FilterAction *)cur->data); + g_slist_free(action_list); +} + +static void filter_cond_free(FilterCond *cond) +{ + g_free(cond->header_name); + g_free(cond->str_value); + g_free(cond); +} + +static void filter_action_free(FilterAction *action) +{ + g_free(action->str_value); + g_free(action); +} + +void filter_info_free(FilterInfo *fltinfo) +{ + g_slist_free(fltinfo->dest_list); + g_free(fltinfo); +} -- cgit v1.2.3