No issues found
1 /*
2 * Code for autogenerating rules or filters from a message.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 * Authors:
18 * Michael Zucchi <notzed@ximian.com>
19 * Ettore Perazzoli <ettore@ximian.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 */
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <ctype.h>
28 #include <string.h>
29
30 #include <glib/gi18n.h>
31
32 #include <libemail-engine/e-mail-folder-utils.h>
33 #include <libemail-engine/e-mail-session.h>
34
35 #include "mail-vfolder-ui.h"
36 #include "mail-autofilter.h"
37 #include "em-utils.h"
38 #include "libevolution-utils/e-alert-dialog.h"
39 #include "e-util/e-util-private.h"
40
41 #include "em-vfolder-editor-context.h"
42 #include "em-vfolder-editor-rule.h"
43 #include "em-vfolder-editor.h"
44
45 #include "em-filter-context.h"
46 #include "em-filter-rule.h"
47 #include "em-filter-editor.h"
48 #include "filter/e-filter-option.h"
49 #include "filter/e-filter-input.h"
50
51 #define d(x)
52
53 static void
54 rule_match_recipients (ERuleContext *context,
55 EFilterRule *rule,
56 CamelInternetAddress *iaddr)
57 {
58 EFilterPart *part;
59 EFilterElement *element;
60 gint i;
61 const gchar *real, *addr;
62 gchar *namestr;
63
64 /* address types etc should handle multiple values */
65 for (i = 0; camel_internet_address_get (iaddr, i, &real, &addr); i++) {
66 part = e_rule_context_create_part (context, "to");
67 e_filter_rule_add_part ((EFilterRule *) rule, part);
68 element = e_filter_part_find_element (part, "recipient-type");
69 e_filter_option_set_current ((EFilterOption *) element, "contains");
70 element = e_filter_part_find_element (part, "recipient");
71 e_filter_input_set_value ((EFilterInput *) element, addr);
72
73 namestr = g_strdup_printf (_("Mail to %s"), real && real[0] ? real : addr);
74 e_filter_rule_set_name (rule, namestr);
75 g_free (namestr);
76 }
77 }
78
79 /* remove 're' part of a subject */
80 static const gchar *
81 strip_re (const gchar *subject)
82 {
83 const guchar *s, *p;
84
85 s = (guchar *) subject;
86
87 while (*s) {
88 while (isspace (*s))
89 s++;
90 if (s[0] == 0)
91 break;
92 if ((s[0] == 'r' || s[0] == 'R')
93 && (s[1] == 'e' || s[1] == 'E')) {
94 p = s + 2;
95 while (isdigit (*p) || (ispunct (*p) && (*p != ':')))
96 p++;
97 if (*p == ':') {
98 s = p + 1;
99 } else
100 break;
101 } else
102 break;
103 }
104
105 return (gchar *) s;
106 }
107
108 static void
109 rule_add_subject (ERuleContext *context,
110 EFilterRule *rule,
111 const gchar *text)
112 {
113 EFilterPart *part;
114 EFilterElement *element;
115
116 /* dont match on empty strings ever */
117 if (*text == 0)
118 return;
119 part = e_rule_context_create_part (context, "subject");
120 e_filter_rule_add_part ((EFilterRule *) rule, part);
121 element = e_filter_part_find_element (part, "subject-type");
122 e_filter_option_set_current ((EFilterOption *) element, "contains");
123 element = e_filter_part_find_element (part, "subject");
124 e_filter_input_set_value ((EFilterInput *) element, text);
125 }
126
127 static void
128 rule_add_sender (ERuleContext *context,
129 EFilterRule *rule,
130 const gchar *text)
131 {
132 EFilterPart *part;
133 EFilterElement *element;
134
135 /* dont match on empty strings ever */
136 if (*text == 0)
137 return;
138 part = e_rule_context_create_part (context, "sender");
139 e_filter_rule_add_part ((EFilterRule *) rule, part);
140 element = e_filter_part_find_element (part, "sender-type");
141 e_filter_option_set_current ((EFilterOption *) element, "contains");
142 element = e_filter_part_find_element (part, "sender");
143 e_filter_input_set_value ((EFilterInput *) element, text);
144 }
145
146 /* do a bunch of things on the subject to try and detect mailing lists, remove
147 * unneeded stuff, etc */
148 static void
149 rule_match_subject (ERuleContext *context,
150 EFilterRule *rule,
151 const gchar *subject)
152 {
153 const gchar *s;
154 const gchar *s1, *s2;
155 gchar *tmp;
156
157 s = strip_re (subject);
158 /* dont match on empty subject */
159 if (*s == 0)
160 return;
161
162 /* [blahblah] is probably a mailing list, match on it separately */
163 s1 = strchr (s, '[');
164 s2 = strchr (s, ']');
165 if (s1 && s2 && s1 < s2) {
166 /* probably a mailing list, match on the mailing list name */
167 tmp = g_alloca (s2 - s1 + 2);
168 memcpy (tmp, s1, s2 - s1 + 1);
169 tmp[s2 - s1 + 1] = 0;
170 g_strstrip (tmp);
171 rule_add_subject (context, rule, tmp);
172 s = s2 + 1;
173 }
174 /* Froblah: at the start is probably something important (e.g. bug number) */
175 s1 = strchr (s, ':');
176 if (s1) {
177 tmp = g_alloca (s1 - s + 1);
178 memcpy (tmp, s, s1 - s);
179 tmp[s1 - s] = 0;
180 g_strstrip (tmp);
181 rule_add_subject (context, rule, tmp);
182 s = s1 + 1;
183 }
184
185 /* just lump the rest together */
186 tmp = g_alloca (strlen (s) + 1);
187 strcpy (tmp, s);
188 g_strstrip (tmp);
189 rule_add_subject (context, rule, tmp);
190 }
191
192 static void
193 rule_match_mlist (ERuleContext *context,
194 EFilterRule *rule,
195 const gchar *mlist)
196 {
197 EFilterPart *part;
198 EFilterElement *element;
199
200 if (mlist[0] == 0)
201 return;
202
203 part = e_rule_context_create_part (context, "mlist");
204 e_filter_rule_add_part (rule, part);
205
206 element = e_filter_part_find_element (part, "mlist-type");
207 e_filter_option_set_current ((EFilterOption *) element, "is");
208
209 element = e_filter_part_find_element (part, "mlist");
210 e_filter_input_set_value ((EFilterInput *) element, mlist);
211 }
212
213 static void
214 rule_from_address (EFilterRule *rule,
215 ERuleContext *context,
216 CamelInternetAddress *addr,
217 gint flags)
218 {
219 rule->grouping = E_FILTER_GROUP_ALL;
220
221 if (flags & AUTO_FROM) {
222 const gchar *name, *address;
223 gchar *namestr;
224
225 camel_internet_address_get (addr, 0, &name, &address);
226 rule_add_sender (context, rule, address);
227 if (name == NULL || name[0] == '\0')
228 name = address;
229 namestr = g_strdup_printf (_("Mail from %s"), name);
230 e_filter_rule_set_name (rule, namestr);
231 g_free (namestr);
232 }
233 if (flags & AUTO_TO) {
234 rule_match_recipients (context, rule, addr);
235 }
236
237 }
238
239 static void
240 rule_from_message (EFilterRule *rule,
241 ERuleContext *context,
242 CamelMimeMessage *msg,
243 gint flags)
244 {
245 CamelInternetAddress *addr;
246
247 rule->grouping = E_FILTER_GROUP_ALL;
248
249 if (flags & AUTO_SUBJECT) {
250 const gchar *subject = msg->subject ? msg->subject : "";
251 gchar *namestr;
252
253 rule_match_subject (context, rule, subject);
254
255 namestr = g_strdup_printf (_("Subject is %s"), strip_re (subject));
256 e_filter_rule_set_name (rule, namestr);
257 g_free (namestr);
258 }
259 /* should parse the from address into an internet address? */
260 if (flags & AUTO_FROM) {
261 CamelInternetAddress *from;
262 gint i;
263 const gchar *name, *address;
264 gchar *namestr;
265
266 from = camel_mime_message_get_from (msg);
267 for (i = 0; from && camel_internet_address_get (
268 from, i, &name, &address); i++) {
269 rule_add_sender (context, rule, address);
270 if (name == NULL || name[0] == '\0')
271 name = address;
272 namestr = g_strdup_printf (_("Mail from %s"), name);
273 e_filter_rule_set_name (rule, namestr);
274 g_free (namestr);
275 }
276 }
277 if (flags & AUTO_TO) {
278 addr = (CamelInternetAddress *)
279 camel_mime_message_get_recipients (
280 msg, CAMEL_RECIPIENT_TYPE_TO);
281 if (addr)
282 rule_match_recipients (context, rule, addr);
283 addr = (CamelInternetAddress *)
284 camel_mime_message_get_recipients (
285 msg, CAMEL_RECIPIENT_TYPE_CC);
286 if (addr)
287 rule_match_recipients (context, rule, addr);
288 }
289 if (flags & AUTO_MLIST) {
290 gchar *name, *mlist;
291
292 mlist = camel_header_raw_check_mailing_list (
293 &((CamelMimePart *) msg)->headers);
294 if (mlist) {
295 rule_match_mlist (context, rule, mlist);
296 name = g_strdup_printf (_("%s mailing list"), mlist);
297 e_filter_rule_set_name (rule, name);
298 g_free (name);
299 }
300 g_free (mlist);
301 }
302 }
303
304 EFilterRule *
305 em_vfolder_rule_from_message (EMVFolderContext *context,
306 CamelMimeMessage *msg,
307 gint flags,
308 CamelFolder *folder)
309 {
310 EFilterRule *rule;
311 EMailSession *session;
312 gchar *uri;
313
314 g_return_val_if_fail (EM_IS_VFOLDER_CONTEXT (context), NULL);
315 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (msg), NULL);
316 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
317
318 uri = e_mail_folder_uri_from_folder (folder);
319
320 session = em_vfolder_editor_context_get_session ((EMVFolderEditorContext *) context);
321
322 rule = em_vfolder_editor_rule_new (session);
323 em_vfolder_rule_add_source (EM_VFOLDER_RULE (rule), uri);
324 rule_from_message (rule, E_RULE_CONTEXT (context), msg, flags);
325
326 g_free (uri);
327
328 return rule;
329 }
330
331 EFilterRule *
332 em_vfolder_rule_from_address (EMVFolderContext *context,
333 CamelInternetAddress *addr,
334 gint flags,
335 CamelFolder *folder)
336 {
337 EFilterRule *rule;
338 EMailSession *session;
339 gchar *uri;
340
341 g_return_val_if_fail (EM_IS_VFOLDER_CONTEXT (context), NULL);
342 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), NULL);
343 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
344
345 uri = e_mail_folder_uri_from_folder (folder);
346
347 session = em_vfolder_editor_context_get_session ((EMVFolderEditorContext *) context);
348
349 rule = em_vfolder_editor_rule_new (session);
350 em_vfolder_rule_add_source (EM_VFOLDER_RULE (rule), uri);
351 rule_from_address (rule, E_RULE_CONTEXT (context), addr, flags);
352
353 g_free (uri);
354
355 return rule;
356 }
357
358 EFilterRule *
359 filter_rule_from_message (EMFilterContext *context,
360 CamelMimeMessage *msg,
361 gint flags)
362 {
363 EFilterRule *rule;
364 EFilterPart *part;
365
366 g_return_val_if_fail (EM_IS_FILTER_CONTEXT (context), NULL);
367 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (msg), NULL);
368
369 rule = em_filter_rule_new ();
370 rule_from_message (rule, E_RULE_CONTEXT (context), msg, flags);
371
372 part = em_filter_context_next_action (context, NULL);
373
374 em_filter_rule_add_action (
375 EM_FILTER_RULE (rule), e_filter_part_clone (part));
376
377 return rule;
378 }
379
380 void
381 filter_gui_add_from_message (EMailSession *session,
382 CamelMimeMessage *msg,
383 const gchar *source,
384 gint flags)
385 {
386 EMFilterContext *fc;
387 const gchar *config_dir;
388 gchar *user, *system;
389 EFilterRule *rule;
390
391 g_return_if_fail (E_IS_MAIL_SESSION (session));
392 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (msg));
393
394 fc = em_filter_context_new (session);
395 config_dir = mail_session_get_config_dir ();
396 user = g_build_filename (config_dir, "filters.xml", NULL);
397 system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL);
398 e_rule_context_load ((ERuleContext *) fc, system, user);
399 g_free (system);
400
401 rule = filter_rule_from_message (fc, msg, flags);
402
403 e_filter_rule_set_source (rule, source);
404
405 e_rule_context_add_rule_gui (
406 (ERuleContext *) fc, rule, _("Add Filter Rule"), user);
407 g_free (user);
408 g_object_unref (fc);
409 }
410
411 void
412 mail_filter_rename_folder (CamelStore *store,
413 const gchar *old_folder_name,
414 const gchar *new_folder_name)
415 {
416 CamelSession *session;
417 EMFilterContext *fc;
418 const gchar *config_dir;
419 gchar *user, *system;
420 GList *changed;
421 gchar *old_uri;
422 gchar *new_uri;
423
424 g_return_if_fail (CAMEL_IS_STORE (store));
425 g_return_if_fail (old_folder_name != NULL);
426 g_return_if_fail (new_folder_name != NULL);
427
428 session = camel_service_get_session (CAMEL_SERVICE (store));
429
430 old_uri = e_mail_folder_uri_build (store, old_folder_name);
431 new_uri = e_mail_folder_uri_build (store, new_folder_name);
432
433 fc = em_filter_context_new (E_MAIL_SESSION (session));
434 config_dir = mail_session_get_config_dir ();
435 user = g_build_filename (config_dir, "filters.xml", NULL);
436 system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL);
437 e_rule_context_load ((ERuleContext *) fc, system, user);
438 g_free (system);
439
440 changed = e_rule_context_rename_uri (
441 (ERuleContext *) fc, old_uri, new_uri, g_str_equal);
442 if (changed) {
443 if (e_rule_context_save ((ERuleContext *) fc, user) == -1)
444 g_warning ("Could not write out changed filter rules\n");
445 e_rule_context_free_uri_list ((ERuleContext *) fc, changed);
446 }
447
448 g_free (user);
449 g_object_unref (fc);
450
451 g_free (old_uri);
452 g_free (new_uri);
453 }
454
455 void
456 mail_filter_delete_folder (CamelStore *store,
457 const gchar *folder_name,
458 EAlertSink *alert_sink)
459 {
460 CamelSession *session;
461 EMFilterContext *fc;
462 const gchar *config_dir;
463 gchar *user, *system;
464 GList *deleted;
465 gchar *uri;
466
467 g_return_if_fail (CAMEL_IS_STORE (store));
468 g_return_if_fail (folder_name != NULL);
469 g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
470
471 session = camel_service_get_session (CAMEL_SERVICE (store));
472
473 uri = e_mail_folder_uri_build (store, folder_name);
474
475 fc = em_filter_context_new (E_MAIL_SESSION (session));
476 config_dir = mail_session_get_config_dir ();
477 user = g_build_filename (config_dir, "filters.xml", NULL);
478 system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL);
479 e_rule_context_load ((ERuleContext *) fc, system, user);
480 g_free (system);
481
482 deleted = e_rule_context_delete_uri (
483 (ERuleContext *) fc, uri, g_str_equal);
484 if (deleted) {
485 GString *s;
486 guint s_count;
487 gchar *info;
488 GList *l;
489
490 s = g_string_new ("");
491 s_count = 0;
492 for (l = deleted; l; l = l->next) {
493 const gchar *name = (const gchar *) l->data;
494
495 if (s_count == 0) {
496 g_string_append (s, name);
497 } else {
498 if (s_count == 1) {
499 g_string_prepend (s, " ");
500 g_string_append (s, "\n");
501 }
502 g_string_append_printf (s, " %s\n", name);
503 }
504 s_count++;
505 }
506
507 info = g_strdup_printf (ngettext (
508 /* Translators: The first %s is name of the affected
509 * filter rule(s), the second %s is URI of the removed
510 * folder. For more than one filter rule is each of
511 * them on a separate line, with four spaces in front
512 * of its name, without quotes. */
513 "The filter rule \"%s\" has been modified to account "
514 "for the deleted folder\n\"%s\".",
515 "The following filter rules\n%s have been modified "
516 "to account for the deleted folder\n\"%s\".",
517 s_count), s->str, folder_name);
518 e_alert_submit (
519 alert_sink, "mail:filter-updated", info, NULL);
520 g_string_free (s, TRUE);
521 g_free (info);
522
523 if (e_rule_context_save ((ERuleContext *) fc, user) == -1)
524 g_warning ("Could not write out changed filter rules\n");
525 e_rule_context_free_uri_list ((ERuleContext *) fc, deleted);
526 }
527
528 g_free (user);
529 g_object_unref (fc);
530 g_free (uri);
531 }