* src/filter.c
[claws.git] / src / filter.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
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 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <string.h>
26 #include <strings.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <regex.h>
30
31 #include "intl.h"
32 #include "procheader.h"
33 #include "filter.h"
34 #include "folder.h"
35 #include "utils.h"
36
37 FolderItem *filter_get_dest_folder(GSList *fltlist, const gchar *file)
38 {
39         static FolderItem *dummy = NULL;
40         FolderItem *dest_folder = NULL;
41         GSList *hlist, *cur;
42         Filter *filter;
43
44         g_return_val_if_fail(file != NULL, NULL);
45         if (!fltlist) return NULL;
46
47         hlist = procheader_get_header_list_from_file(file);
48         if (!hlist) return NULL;
49
50         for (cur = fltlist; cur != NULL; cur = cur->next) {
51                 filter = (Filter *)cur->data;
52                 if (filter_match_condition(filter, hlist)) {
53                         if (filter->action == FLT_NOTRECV) {
54                                 if (!dummy) {
55                                         dummy = g_new0(FolderItem, 1);
56                                         dummy->path = g_strdup(FILTER_NOT_RECEIVE);
57                                 }
58                                 dest_folder = dummy;
59                         } else
60                                 dest_folder = folder_find_item_from_identifier
61                                         (filter->dest);
62                         break;
63                 }
64         }
65
66         if (dummy != NULL) {
67                 g_free(dummy->path);
68                 g_free(dummy);
69         }
70
71         procheader_header_list_destroy(hlist);
72
73         return dest_folder;
74 }
75
76 static gboolean strfind(const gchar *haystack, const gchar *needle)
77 {
78         return strstr(haystack, needle) != NULL ? TRUE : FALSE;
79 }
80
81 static gboolean strnotfind(const gchar *haystack, const gchar *needle)
82 {
83         return strstr(haystack, needle) != NULL ? FALSE : TRUE;
84 }
85
86 static gboolean strcasefind(const gchar *haystack, const gchar *needle)
87 {
88         return strcasestr(haystack, needle) != NULL ? TRUE : FALSE;
89 }
90
91 static gboolean strcasenotfind(const gchar *haystack, const gchar *needle)
92 {
93         return strcasestr(haystack, needle) != NULL ? FALSE : TRUE;
94 }
95
96 static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
97 {
98         gint ret = 0;
99         regex_t preg;
100         regmatch_t pmatch[1];
101
102         ret = regcomp(&preg, needle, 0);
103         if (ret != 0) return FALSE;
104
105         ret = regexec(&preg, haystack, 1, pmatch, 0);
106         regfree(&preg);
107
108         if (ret == REG_NOMATCH) return FALSE;
109
110         if (pmatch[0].rm_so != -1)
111                 return TRUE;
112         else
113                 return FALSE;
114 }
115
116 gboolean filter_match_condition(Filter *filter, GSList *hlist)
117 {
118         Header *header;
119         gboolean (*StrFind1)    (const gchar *hs, const gchar *nd);
120         gboolean (*StrFind2)    (const gchar *hs, const gchar *nd);
121
122         g_return_val_if_fail(filter->name1 != NULL, FALSE);
123
124         if (FLT_IS_REGEX(filter->flag1))
125                 StrFind1 = strmatch_regex;
126         else if (FLT_IS_CASE_SENS(filter->flag1))
127                 StrFind1 = FLT_IS_CONTAIN(filter->flag1)
128                         ? strfind : strnotfind;
129         else
130                 StrFind1 = FLT_IS_CONTAIN(filter->flag1)
131                         ? strcasefind : strcasenotfind;
132
133         if (FLT_IS_REGEX(filter->flag2))
134                 StrFind2 = strmatch_regex;
135         if (FLT_IS_CASE_SENS(filter->flag2))
136                 StrFind2 = FLT_IS_CONTAIN(filter->flag2)
137                         ? strfind : strnotfind;
138         else
139                 StrFind2 = FLT_IS_CONTAIN(filter->flag2)
140                         ? strcasefind : strcasenotfind;
141
142         if (filter->cond == FLT_AND) {
143                 gboolean match1 = FALSE, match2 = FALSE;
144
145                 /* ignore second condition if not set */
146                 if (!filter->name2) match2 = TRUE;
147
148                 for (; hlist != NULL; hlist = hlist->next) {
149                         header = hlist->data;
150
151                         if (!match1 &&
152                             procheader_headername_equal(header->name,
153                                                         filter->name1)) {
154                                 if (!filter->body1 ||
155                                     StrFind1(header->body, filter->body1))
156                                         match1 = TRUE;
157                         }
158                         if (!match2 &&
159                             procheader_headername_equal(header->name,
160                                                          filter->name2)) {
161                                 if (!filter->body2 ||
162                                     StrFind2(header->body, filter->body2))
163                                         match2 = TRUE;
164                         }
165
166                         if (match1 && match2) return TRUE;
167                 }
168         } else if (filter->cond == FLT_OR) {
169                 for (; hlist != NULL; hlist = hlist->next) {
170                         header = hlist->data;
171
172                         if (procheader_headername_equal(header->name,
173                                                         filter->name1))
174                                 if (!filter->body1 ||
175                                     StrFind1(header->body, filter->body1))
176                                         return TRUE;
177                         if (filter->name2 &&
178                             procheader_headername_equal(header->name,
179                                                         filter->name2))
180                                 if (!filter->body2 ||
181                                     StrFind2(header->body, filter->body2))
182                                         return TRUE;
183                 }
184         }
185
186         return FALSE;
187 }
188
189 gchar *filter_get_str(Filter *filter)
190 {
191         gchar *str;
192
193         str = g_strdup_printf
194                 ("%s\t%s\t%c\t%s\t%s\t%s\t%d\t%d\t%c",
195                  filter->name1, filter->body1 ? filter->body1 : "",
196                  filter->name2 ? (filter->cond == FLT_AND ? '&' : '|') : ' ',
197                  filter->name2 ? filter->name2 : "",
198                  filter->body2 ? filter->body2 : "",
199                  filter->dest ? filter->dest : "",
200                  (guint)filter->flag1, (guint)filter->flag2,
201                  filter->action == FLT_MOVE    ? 'm' :
202                  filter->action == FLT_NOTRECV ? 'n' :
203                  filter->action == FLT_DELETE  ? 'd' : ' ');
204
205         return str;
206 }
207
208 #define PARSE_ONE_PARAM(p, srcp) \
209 { \
210         p = strchr(srcp, '\t'); \
211         if (!p) return NULL; \
212         else \
213                 *p++ = '\0'; \
214 }
215
216 Filter *filter_read_str(const gchar *str)
217 {
218         Filter *filter;
219         gchar *tmp;
220         gchar *name1, *body1, *op, *name2, *body2, *dest;
221         gchar *flag1 = NULL, *flag2 = NULL, *action = NULL;
222
223         Xstrdup_a(tmp, str, return NULL);
224
225         name1 = tmp;
226         PARSE_ONE_PARAM(body1, name1);
227         PARSE_ONE_PARAM(op, body1);
228         PARSE_ONE_PARAM(name2, op);
229         PARSE_ONE_PARAM(body2, name2);
230         PARSE_ONE_PARAM(dest, body2);
231         if (strchr(dest, '\t')) {
232                 gchar *p;
233
234                 PARSE_ONE_PARAM(flag1, dest);
235                 PARSE_ONE_PARAM(flag2, flag1);
236                 PARSE_ONE_PARAM(action, flag2);
237                 if ((p = strchr(action, '\t'))) *p = '\0';
238         }
239
240         filter = g_new0(Filter, 1);
241         filter->name1 = *name1 ? g_strdup(name1) : NULL;
242         filter->body1 = *body1 ? g_strdup(body1) : NULL;
243         filter->name2 = *name2 ? g_strdup(name2) : NULL;
244         filter->body2 = *body2 ? g_strdup(body2) : NULL;
245         filter->cond = (*op == '|') ? FLT_OR : FLT_AND;
246         filter->dest = *dest ? g_strdup(dest) : NULL;
247
248         filter->flag1 = FLT_CONTAIN;
249         filter->flag2 = FLT_CONTAIN;
250         if (flag1) filter->flag1 = (FilterFlag)strtoul(flag1, NULL, 10);
251         if (flag2) filter->flag2 = (FilterFlag)strtoul(flag2, NULL, 10);
252
253         if (!strcmp2(dest, FILTER_NOT_RECEIVE))
254                 filter->action = FLT_NOTRECV;
255         else
256                 filter->action = FLT_MOVE;
257         if (action) {
258                 switch (*action) {
259                 case 'm': filter->action = FLT_MOVE;    break;
260                 case 'n': filter->action = FLT_NOTRECV; break;
261                 case 'd': filter->action = FLT_DELETE;  break;
262                 default:  g_warning("Invalid action: `%c'\n", *action);
263                 }
264         }
265
266         return filter;
267 }
268
269 void filter_free(Filter *filter)
270 {
271         if (!filter) return;
272
273         g_free(filter->name1);
274         g_free(filter->body1);
275
276         g_free(filter->name2);
277         g_free(filter->body2);
278
279         g_free(filter->dest);
280
281         g_free(filter);
282 }