1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3 *
4 * Copyright (C) 1997-2013 Stuart Parmenter and others,
5 * See the file AUTHORS for a list.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 * 02111-1307, USA.
21 */
22 /*
23 * filter.c
24 *
25 * The mail filtering porting of balsa
26 *
27 * Mostly skeletonic
28 *
29 * Primary author: Emmanuel ALLAUD
30 */
31
32 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
33 # include "config.h"
34 #endif /* HAVE_CONFIG_H */
35 #include "filter.h"
36
37 #include <ctype.h>
38 #include <string.h>
39
40 #if HAVE_CANBERRA
41 #include <canberra-gtk.h>
42 #endif /* HAVE_CANBERRA */
43
44 #include "libbalsa.h"
45 #include "libbalsa_private.h"
46
47 #include "filter-funcs.h"
48 #include "filter-private.h"
49 #include "misc.h"
50 #include <glib/gi18n.h>
51
52 /* filters_trash_mbox points to a mailbox that is used as Trash by filters
53 * code. */
54 static LibBalsaMailbox* filters_trash_mbox = NULL;
55
56 static UrlToMailboxMapper url_to_mailbox_mapper = NULL;
57 static GSList** filter_list = NULL;
58 void
libbalsa_filters_set_trash(LibBalsaMailbox * new_trash)59 libbalsa_filters_set_trash(LibBalsaMailbox* new_trash)
60 {
61 filters_trash_mbox = new_trash;
62 }
63
64 void
libbalsa_filters_set_url_mapper(UrlToMailboxMapper u2mm)65 libbalsa_filters_set_url_mapper(UrlToMailboxMapper u2mm)
66 {
67 url_to_mailbox_mapper = u2mm;
68 }
69 void
libbalsa_filters_set_filter_list(GSList ** list)70 libbalsa_filters_set_filter_list(GSList** list)
71 {
72 filter_list = list;
73 }
74
75 /*------------------------------------------------------------------------
76 ---- Helper functions (also exported to have a fine-grained API) -------
77 */
78 /* libbalsa_condition_regex_set:
79 steals the string.
80 */
81 void
libbalsa_condition_regex_set(LibBalsaConditionRegex * reg,gchar * str)82 libbalsa_condition_regex_set(LibBalsaConditionRegex * reg, gchar *str)
83 {
84 g_free(reg->string);
85 reg->string = str;
86 }
87
88 const gchar*
libbalsa_condition_regex_get(LibBalsaConditionRegex * reg)89 libbalsa_condition_regex_get(LibBalsaConditionRegex * reg)
90 {
91 return reg->string;
92 }
93
94 void
libbalsa_condition_prepend_regex(LibBalsaCondition * cond,LibBalsaConditionRegex * new_reg)95 libbalsa_condition_prepend_regex(LibBalsaCondition* cond,
96 LibBalsaConditionRegex * new_reg)
97 {
98 g_warning("%s: fixme!\n", __func__);
99 #if 0
100 cond->match.regexs =
101 g_slist_prepend(cond->match.regexs, new_reg);
102 #endif
103 }
104
105 gboolean
libbalsa_condition_matches(LibBalsaCondition * cond,LibBalsaMessage * message)106 libbalsa_condition_matches(LibBalsaCondition* cond,
107 LibBalsaMessage * message)
108 {
109 gboolean match = FALSE;
110 gchar * str;
111 GString * body;
112 gboolean will_ref;
113
114 g_return_val_if_fail(cond, FALSE);
115 g_return_val_if_fail(message->headers != NULL, FALSE);
116
117 switch (cond->type) {
118 case CONDITION_STRING:
119 will_ref =
120 (CONDITION_CHKMATCH(cond,CONDITION_MATCH_CC) ||
121 CONDITION_CHKMATCH(cond,CONDITION_MATCH_BODY));
122 if(will_ref) {
123 gboolean is_refed =
124 libbalsa_message_body_ref(message, FALSE, FALSE);
125 if (!is_refed) {
126 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
127 _("Unable to load message body to "
128 "match filter"));
129 return FALSE; /* We don't want to match if an error occurred */
130 }
131 }
132 /* do the work */
133 if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_TO)
134 && message->headers->to_list) {
135 str =
136 internet_address_list_to_string(message->headers->to_list,
137 FALSE);
138 match=libbalsa_utf8_strstr(str,cond->match.string.string);
139 g_free(str);
140 if(match) break;
141 }
142 if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_FROM)
143 && message->headers->from) {
144 str =
145 internet_address_list_to_string(message->headers->from,
146 FALSE);
147 match=libbalsa_utf8_strstr(str,cond->match.string.string);
148 g_free(str);
149 if (match) break;
150 }
151 if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_SUBJECT)) {
152 if (libbalsa_utf8_strstr(LIBBALSA_MESSAGE_GET_SUBJECT(message),
153 cond->match.string.string)) {
154 match = TRUE;
155 break;
156 }
157 }
158 if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_CC)
159 && message->headers->cc_list) {
160 str =
161 internet_address_list_to_string(message->headers->cc_list,
162 FALSE);
163 match=libbalsa_utf8_strstr(str,cond->match.string.string);
164 g_free(str);
165 if (match) break;
166 }
167 if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_US_HEAD)) {
168 if (cond->match.string.user_header) {
169 const gchar *header =
170 libbalsa_message_get_user_header(message,
171 cond->match.string.
172 user_header);
173
174 if (libbalsa_utf8_strstr(header,
175 cond->match.string.string)) {
176 match = TRUE;
177 break;
178 }
179 }
180 }
181 if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_BODY)) {
182 if (!message->mailbox)
183 return FALSE; /* We don't want to match if an error occurred */
184 body =
185 content2reply(message->body_list, NULL, 0, FALSE, FALSE);
186 if (body) {
187 if (body->str)
188 match = libbalsa_utf8_strstr(body->str,
189 cond->match.string.string);
190 g_string_free(body,TRUE);
191 }
192 }
193 if(will_ref) libbalsa_message_body_unref(message);
194 break;
195 case CONDITION_REGEX:
196 break;
197 case CONDITION_DATE:
198 match = message->headers->date>=cond->match.date.date_low
199 && (cond->match.date.date_high==0 ||
200 message->headers->date<=cond->match.date.date_high);
201 break;
202 case CONDITION_FLAG:
203 match = LIBBALSA_MESSAGE_HAS_FLAG(message, cond->match.flags);
204 break;
205 case CONDITION_AND:
206 match =
207 libbalsa_condition_matches(cond->match.andor.left, message) &&
208 libbalsa_condition_matches(cond->match.andor.right, message);
209 break;
210 case CONDITION_OR:
211 match =
212 libbalsa_condition_matches(cond->match.andor.left, message) ||
213 libbalsa_condition_matches(cond->match.andor.right, message);
214 break;
215 case CONDITION_NONE:
216 break;
217 }
218 /* To avoid warnings */
219 return cond->negate ? !match : match;
220 }
221
222 /*--------- Filtering functions -------------------------------*/
223
224 gint
filters_prepare_to_run(GSList * filters)225 filters_prepare_to_run(GSList * filters)
226 {
227 LibBalsaFilter* fil;
228 gboolean ok=TRUE;
229
230 for(;filters;filters=g_slist_next(filters)) {
231 fil=(LibBalsaFilter*) filters->data;
232 if (!FILTER_CHKFLAG(fil,FILTER_VALID)) {
233 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
234 _("Invalid filter: %s"),fil->name);
235 ok=FALSE;
236 }
237 else if (!FILTER_CHKFLAG(fil,FILTER_COMPILED))
238 ok=libbalsa_filter_compile_regexs(fil);
239 }
240
241 return ok;
242 }
243
244 /* Apply the filter's action to the messages in the list; returns TRUE
245 * if message(s) were moved to the trash. */
246 gboolean
libbalsa_filter_mailbox_messages(LibBalsaFilter * filt,LibBalsaMailbox * mailbox,GArray * msgnos)247 libbalsa_filter_mailbox_messages(LibBalsaFilter * filt,
248 LibBalsaMailbox * mailbox,
249 GArray * msgnos)
250 {
251 gboolean result=FALSE;
252 LibBalsaMailbox *mbox;
253 GError *err = NULL;
254 gchar **parts, **p;
255
256 if (msgnos->len == 0)
257 return FALSE;
258
259 #if HAVE_CANBERRA
260 if (filt->sound) {
261 GdkScreen *screen;
262 gint rc;
263
264 screen = gdk_screen_get_default();
265 rc = ca_context_play(ca_gtk_context_get_for_screen(screen), 0,
266 CA_PROP_MEDIA_FILENAME, filt->sound, NULL);
267 g_message("(%s) play %s, %s", __func__, filt->sound, ca_strerror(rc));
268 }
269 #endif /* HAVE_CANBERRA */
270 if (filt->popup_text)
271 libbalsa_information(LIBBALSA_INFORMATION_MESSAGE,
272 "%s",
273 filt->popup_text);
274
275 libbalsa_lock_mailbox(mailbox);
276
277 switch (filt->action) {
278 case FILTER_COPY:
279 mbox = url_to_mailbox_mapper(filt->action_string);
280 if (!mbox)
281 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
282 _("Bad mailbox name for filter: %s"),
283 filt->name);
284 else if (!libbalsa_mailbox_messages_copy(mailbox, msgnos, mbox, &err))
285 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
286 _("Error when copying messages: %s"),
287 err ? err->message : "?");
288 else if (mbox == filters_trash_mbox)
289 result = TRUE;
290 break;
291 case FILTER_TRASH:
292 if (!filters_trash_mbox ||
293 !libbalsa_mailbox_messages_move(mailbox, msgnos,
294 filters_trash_mbox, &err))
295 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
296 _("Error when trashing messages: %s"),
297 err ? err->message : "?");
298 else
299 result = TRUE;
300 break;
301 case FILTER_MOVE:
302 mbox = url_to_mailbox_mapper(filt->action_string);
303 if (!mbox)
304 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
305 _("Bad mailbox name for filter: %s"),
306 filt->name);
307 else if (!libbalsa_mailbox_messages_move(mailbox, msgnos, mbox, &err))
308 libbalsa_information(LIBBALSA_INFORMATION_ERROR,
309 _("Error when moving messages: %s"),
310 err ? err->message : "?");
311 else if (mbox == filters_trash_mbox)
312 result = TRUE;
313 break;
314 case FILTER_COLOR:
315 parts = g_strsplit(filt->action_string, ";", 2);
316 for (p = parts; *p; p++) {
317 if (g_str_has_prefix(*p, "foreground:"))
318 libbalsa_mailbox_set_foreground(mailbox, msgnos,
319 (*p) + 11);
320 if (g_str_has_prefix(*p, "background:"))
321 libbalsa_mailbox_set_background(mailbox, msgnos,
322 (*p) + 11);
323 }
324 g_strfreev(parts);
325 break;
326 case FILTER_PRINT:
327 /* FIXME : to be implemented */
328 break;
329 case FILTER_RUN:
330 /* FIXME : to be implemented */
331 break;
332 case FILTER_NOTHING:
333 case FILTER_N_TYPES:
334 /* Nothing to do */
335 break;
336 }
337 g_clear_error(&err);
338 libbalsa_unlock_mailbox(mailbox);
339
340 return result;
341 }
342
343 /*--------- End of Filtering functions -------------------------------*/
344
345 LibBalsaFilter*
libbalsa_filter_get_by_name(const gchar * fname)346 libbalsa_filter_get_by_name(const gchar * fname)
347 {
348 GSList * list;
349 gint fnamelen;
350
351 g_return_val_if_fail(filter_list, NULL);
352 if (!fname || fname[0]=='\0') return NULL;
353
354 fnamelen = strlen(fname);
355 for (list = *filter_list;list;list = g_slist_next(list)) {
356 gint len = strlen(((LibBalsaFilter*)list->data)->name);
357
358 if (strncmp(fname,((LibBalsaFilter*)list->data)->name,
359 MAX(len,fnamelen))==0)
360 return (LibBalsaFilter*)list->data;
361 }
362 return NULL;
363 }
364
365 /* Check whether the condition tests the message body, and if so,
366 * whether it's already loaded; used by the imap mailbox driver to
367 * decide whether to do a server-side match. */
368 gboolean
libbalsa_condition_can_match(LibBalsaCondition * cond,LibBalsaMessage * message)369 libbalsa_condition_can_match(LibBalsaCondition * cond,
370 LibBalsaMessage * message)
371 {
372 if (!message)
373 return FALSE;
374
375 switch (cond->type) {
376 case CONDITION_STRING:
377 return !(CONDITION_CHKMATCH(cond, CONDITION_MATCH_BODY)
378 && message->body_list == NULL)
379 && !(CONDITION_CHKMATCH(cond, CONDITION_MATCH_US_HEAD)
380 && message->mime_msg == NULL);
381 case CONDITION_AND:
382 case CONDITION_OR:
383 return libbalsa_condition_can_match(cond->match.andor.left,
384 message)
385 && libbalsa_condition_can_match(cond->match.andor.right,
386 message);
387 default:
388 return TRUE;
389 }
390 }
391
392 /* Check whether a condition looks only at flags; if it does, test
393 * whether the given message's flags match it, and return the result in
394 * *match; used by mailbox backends to decide when the full
395 * LibBalsaMessage is needed. */
396 gboolean
libbalsa_condition_is_flag_only(LibBalsaCondition * cond,LibBalsaMailbox * mailbox,guint msgno,gboolean * match)397 libbalsa_condition_is_flag_only(LibBalsaCondition * cond,
398 LibBalsaMailbox * mailbox,
399 guint msgno,
400 gboolean * match)
401 {
402 gboolean retval;
403 gboolean left_match, right_match;
404
405 switch (cond->type) {
406 case CONDITION_FLAG:
407 if (match)
408 *match =
409 libbalsa_mailbox_msgno_has_flags(mailbox, msgno,
410 cond->match.flags, 0);
411 retval = TRUE;
412 break;
413 case CONDITION_AND:
414 retval =
415 libbalsa_condition_is_flag_only(cond->match.andor.left,
416 mailbox, msgno,
417 match ? &left_match : NULL)
418 && libbalsa_condition_is_flag_only(cond->match.andor.right,
419 mailbox, msgno,
420 match ? &right_match : NULL);
421 if (retval && match)
422 *match = left_match && right_match;
423 break;
424 case CONDITION_OR:
425 retval =
426 libbalsa_condition_is_flag_only(cond->match.andor.left,
427 mailbox, msgno,
428 match ? &left_match : NULL)
429 && libbalsa_condition_is_flag_only(cond->match.andor.right,
430 mailbox, msgno,
431 match ? &right_match : NULL);
432 if (retval && match)
433 *match = left_match || right_match;
434 break;
435 default:
436 return FALSE;
437 }
438
439 if (retval && match && cond->negate)
440 *match = !*match;
441
442 return retval;
443 }
444