1 /*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2015 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library 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 GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <string.h>
29 #include <strings.h>
30 #include <stdlib.h>
31 #include <sys/types.h>
32 #if USE_ONIGURUMA
33 # include <oniguruma.h>
34 #elif HAVE_REGEX_H
35 # include <regex.h>
36 #endif
37 #include <time.h>
38
39 #include "filter.h"
40 #include "procmsg.h"
41 #include "procheader.h"
42 #include "folder.h"
43 #include "utils.h"
44 #include "xml.h"
45 #include "prefs.h"
46 #include "prefs_common.h"
47 #include "prefs_account.h"
48 #include "account.h"
49
50 typedef enum
51 {
52 FLT_O_CONTAIN = 1 << 0,
53 FLT_O_CASE_SENS = 1 << 1,
54 FLT_O_REGEX = 1 << 2
55 } FilterOldFlag;
56
57 static FilterInAddressBookFunc default_addrbook_func = NULL;
58
59 static gboolean filter_match_cond (FilterCond *cond,
60 MsgInfo *msginfo,
61 GSList *hlist,
62 FilterInfo *fltinfo);
63 static gboolean filter_match_header_cond(FilterCond *cond,
64 GSList *hlist);
65 static gboolean filter_match_in_addressbook
66 (FilterCond *cond,
67 GSList *hlist,
68 FilterInfo *fltinfo);
69
70 static void filter_cond_free (FilterCond *cond);
71 static void filter_action_free (FilterAction *action);
72
73
filter_apply(GSList * fltlist,const gchar * file,FilterInfo * fltinfo)74 gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo)
75 {
76 MsgInfo *msginfo;
77 gint ret = 0;
78
79 g_return_val_if_fail(file != NULL, -1);
80 g_return_val_if_fail(fltinfo != NULL, -1);
81
82 if (!fltlist) return 0;
83
84 msginfo = procheader_parse_file(file, fltinfo->flags, FALSE);
85 if (!msginfo) return 0;
86 msginfo->file_path = g_strdup(file);
87
88 /* inherit MIME flag */
89 fltinfo->flags.tmp_flags =
90 (fltinfo->flags.tmp_flags & ~MSG_CACHED_FLAG_MASK) |
91 (msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK);
92
93 ret = filter_apply_msginfo(fltlist, msginfo, fltinfo);
94
95 procmsg_msginfo_free(msginfo);
96
97 return ret;
98 }
99
filter_apply_msginfo(GSList * fltlist,MsgInfo * msginfo,FilterInfo * fltinfo)100 gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo,
101 FilterInfo *fltinfo)
102 {
103 gchar *file;
104 GSList *hlist, *cur;
105 FilterRule *rule;
106 gint ret = 0;
107
108 g_return_val_if_fail(msginfo != NULL, -1);
109 g_return_val_if_fail(fltinfo != NULL, -1);
110
111 fltinfo->error = FLT_ERROR_OK;
112
113 if (!fltlist) return 0;
114
115 file = procmsg_get_message_file(msginfo);
116 if (!file)
117 return -1;
118 hlist = procheader_get_header_list_from_file(file);
119 if (!hlist) {
120 g_free(file);
121 return 0;
122 }
123
124 procmsg_set_auto_decrypt_message(FALSE);
125
126 for (cur = fltlist; cur != NULL; cur = cur->next) {
127 gboolean matched;
128
129 rule = (FilterRule *)cur->data;
130 if (!rule->enabled) continue;
131 matched = filter_match_rule(rule, msginfo, hlist, fltinfo);
132 if (fltinfo->error != FLT_ERROR_OK) {
133 g_warning("filter_match_rule() returned error (code: %d)\n", fltinfo->error);
134 }
135 if (matched) {
136 debug_print("filter-log: %s: rule [%s] matched\n",
137 G_STRFUNC, rule->name ? rule->name : "(No name)");
138 ret = filter_action_exec(rule, msginfo, file, fltinfo);
139 if (ret < 0) {
140 g_warning("filter_action_exec() returned error (code: %d)\n", fltinfo->error);
141 break;
142 }
143 if (fltinfo->drop_done == TRUE ||
144 fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE)
145 break;
146 }
147 }
148
149 procmsg_set_auto_decrypt_message(TRUE);
150
151 procheader_header_list_destroy(hlist);
152 g_free(file);
153
154 return ret;
155 }
156
filter_action_exec(FilterRule * rule,MsgInfo * msginfo,const gchar * file,FilterInfo * fltinfo)157 gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file,
158 FilterInfo *fltinfo)
159 {
160 FolderItem *dest_folder = NULL;
161 FilterAction *action;
162 GSList *cur;
163 gchar *cmdline;
164 gboolean copy_to_self = FALSE;
165 gint ret;
166
167 g_return_val_if_fail(rule != NULL, -1);
168 g_return_val_if_fail(msginfo != NULL, -1);
169 g_return_val_if_fail(file != NULL, -1);
170 g_return_val_if_fail(fltinfo != NULL, -1);
171
172 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
173 action = (FilterAction *)cur->data;
174
175 switch (action->type) {
176 case FLT_ACTION_MARK:
177 debug_print("filter_action_exec(): mark\n");
178 MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED);
179 fltinfo->actions[action->type] = TRUE;
180 break;
181 case FLT_ACTION_COLOR_LABEL:
182 debug_print("filter_action_exec(): color label: %d\n",
183 action->int_value);
184 MSG_UNSET_PERM_FLAGS(fltinfo->flags,
185 MSG_CLABEL_FLAG_MASK);
186 MSG_SET_COLORLABEL_VALUE(fltinfo->flags,
187 action->int_value);
188 fltinfo->actions[action->type] = TRUE;
189 break;
190 case FLT_ACTION_MARK_READ:
191 debug_print("filter_action_exec(): mark as read\n");
192 if (msginfo->folder) {
193 if (MSG_IS_NEW(fltinfo->flags))
194 msginfo->folder->new--;
195 if (MSG_IS_UNREAD(fltinfo->flags))
196 msginfo->folder->unread--;
197 }
198 MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD);
199 fltinfo->actions[action->type] = TRUE;
200 break;
201 case FLT_ACTION_EXEC:
202 cmdline = g_strconcat(action->str_value, " \"", file,
203 "\"", NULL);
204 ret = execute_command_line(cmdline, FALSE);
205 fltinfo->last_exec_exit_status = ret;
206 if (ret == -1) {
207 fltinfo->error = FLT_ERROR_EXEC_FAILED;
208 g_warning("filter_action_exec: cannot execute command: %s", cmdline);
209 g_free(cmdline);
210 return -1;
211 }
212 g_free(cmdline);
213 fltinfo->actions[action->type] = TRUE;
214 break;
215 case FLT_ACTION_EXEC_ASYNC:
216 cmdline = g_strconcat(action->str_value, " \"", file,
217 "\"", NULL);
218 ret = execute_command_line(cmdline, TRUE);
219 fltinfo->last_exec_exit_status = ret;
220 if (ret == -1) {
221 fltinfo->error = FLT_ERROR_EXEC_FAILED;
222 g_warning("filter_action_exec: cannot execute command: %s", cmdline);
223 g_free(cmdline);
224 return -1;
225 }
226 g_free(cmdline);
227 fltinfo->actions[action->type] = TRUE;
228 break;
229 default:
230 break;
231 }
232 }
233
234 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
235 action = (FilterAction *)cur->data;
236
237 switch (action->type) {
238 case FLT_ACTION_MOVE:
239 case FLT_ACTION_COPY:
240 dest_folder = folder_find_item_from_identifier
241 (action->str_value);
242 if (!dest_folder) {
243 g_warning("dest folder '%s' not found\n",
244 action->str_value);
245 fltinfo->error = FLT_ERROR_ERROR;
246 return -1;
247 }
248 debug_print("filter_action_exec(): %s: dest_folder = %s\n",
249 action->type == FLT_ACTION_COPY ?
250 "copy" : "move", action->str_value);
251
252 if (msginfo->folder) {
253 gint val;
254
255 /* local filtering */
256 if (msginfo->folder == dest_folder)
257 copy_to_self = TRUE;
258 else {
259 if (action->type == FLT_ACTION_COPY) {
260 MsgFlags save_flags;
261
262 save_flags = msginfo->flags;
263 msginfo->flags = fltinfo->flags;
264 val = folder_item_copy_msg
265 (dest_folder, msginfo);
266 msginfo->flags = save_flags;
267 if (val == -1) {
268 fltinfo->error = FLT_ERROR_ERROR;
269 return -1;
270 }
271 }
272 fltinfo->actions[action->type] = TRUE;
273 }
274 } else {
275 MsgFlags save_flags;
276
277 save_flags = msginfo->flags;
278 msginfo->flags = fltinfo->flags;
279 if (folder_item_add_msg_msginfo
280 (dest_folder, msginfo, FALSE) < 0) {
281 msginfo->flags = save_flags;
282 fltinfo->error = FLT_ERROR_ERROR;
283 return -1;
284 }
285 msginfo->flags = save_flags;
286 fltinfo->actions[action->type] = TRUE;
287 }
288
289 fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
290 dest_folder);
291 if (action->type == FLT_ACTION_MOVE) {
292 fltinfo->move_dest = dest_folder;
293 fltinfo->drop_done = TRUE;
294 }
295 break;
296 default:
297 break;
298 }
299 }
300
301 if (fltinfo->drop_done == TRUE)
302 return 0;
303
304 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
305 action = (FilterAction *)cur->data;
306
307 switch (action->type) {
308 case FLT_ACTION_NOT_RECEIVE:
309 debug_print("filter_action_exec(): don't receive\n");
310 fltinfo->drop_done = TRUE;
311 fltinfo->actions[action->type] = TRUE;
312 return 0;
313 case FLT_ACTION_DELETE:
314 debug_print("filter_action_exec(): delete\n");
315 if (msginfo->folder) {
316 /* local filtering */
317 if (copy_to_self == FALSE)
318 fltinfo->actions[action->type] = TRUE;
319 } else
320 fltinfo->actions[action->type] = TRUE;
321
322 fltinfo->drop_done = TRUE;
323 return 0;
324 case FLT_ACTION_STOP_EVAL:
325 debug_print("filter_action_exec(): stop evaluation\n");
326 fltinfo->actions[action->type] = TRUE;
327 return 0;
328 default:
329 break;
330 }
331 }
332
333 return 0;
334 }
335
strmatch_regex(const gchar * haystack,const gchar * needle)336 static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
337 {
338 #ifdef USE_ONIGURUMA
339 gint ret = 0;
340 OnigRegex reg;
341 OnigErrorInfo err_info;
342 const UChar *ptn = (const UChar *)needle;
343 const UChar *str = (const UChar *)haystack;
344 size_t haystack_len;
345
346 ret = onig_new(®, ptn, ptn + strlen(needle),
347 /* ONIG_OPTION_EXTEND requires spaces to be escaped */
348 /* ONIG_OPTION_IGNORECASE|ONIG_OPTION_EXTEND, */
349 ONIG_OPTION_IGNORECASE,
350 ONIG_ENCODING_UTF8, ONIG_SYNTAX_POSIX_EXTENDED,
351 &err_info);
352 if (ret != ONIG_NORMAL) {
353 g_warning("strmatch_regex: onig_new() failed: %d", ret);
354 return FALSE;
355 }
356
357 haystack_len = strlen(haystack);
358 ret = onig_search(reg, str, str + haystack_len,
359 str, str + haystack_len, NULL, 0);
360 onig_free(reg);
361
362 if (ret >= 0)
363 return TRUE;
364 else
365 return FALSE;
366 #elif defined(HAVE_REGCOMP)
367 gint ret = 0;
368 regex_t preg;
369
370 ret = regcomp(&preg, needle, REG_ICASE|REG_EXTENDED);
371 if (ret != 0)
372 return FALSE;
373
374 ret = regexec(&preg, haystack, 0, NULL, 0);
375 regfree(&preg);
376
377 if (ret == 0)
378 return TRUE;
379 else
380 return FALSE;
381 #else
382 return FALSE;
383 #endif
384 }
385
filter_match_rule(FilterRule * rule,MsgInfo * msginfo,GSList * hlist,FilterInfo * fltinfo)386 gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist,
387 FilterInfo *fltinfo)
388 {
389 FilterCond *cond;
390 GSList *cur;
391 gboolean matched;
392
393 g_return_val_if_fail(rule->cond_list != NULL, FALSE);
394
395 switch (rule->timing) {
396 case FLT_TIMING_ANY:
397 break;
398 case FLT_TIMING_ON_RECEIVE:
399 if (msginfo->folder != NULL)
400 return FALSE;
401 break;
402 case FLT_TIMING_MANUAL:
403 if (msginfo->folder == NULL)
404 return FALSE;
405 break;
406 default:
407 break;
408 }
409
410 if (rule->bool_op == FLT_AND) {
411 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
412 cond = (FilterCond *)cur->data;
413 if (cond->type >= FLT_COND_SIZE_GREATER) {
414 matched = filter_match_cond
415 (cond, msginfo, hlist, fltinfo);
416 if (matched == FALSE)
417 return FALSE;
418 }
419 }
420
421 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
422 cond = (FilterCond *)cur->data;
423 if (cond->type <= FLT_COND_TO_OR_CC) {
424 matched = filter_match_cond
425 (cond, msginfo, hlist, fltinfo);
426 if (matched == FALSE)
427 return FALSE;
428 }
429 }
430
431 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
432 cond = (FilterCond *)cur->data;
433 if (cond->type == FLT_COND_BODY ||
434 cond->type == FLT_COND_CMD_TEST) {
435 matched = filter_match_cond
436 (cond, msginfo, hlist, fltinfo);
437 if (matched == FALSE)
438 return FALSE;
439 }
440 }
441
442 return TRUE;
443 } else if (rule->bool_op == FLT_OR) {
444 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
445 cond = (FilterCond *)cur->data;
446 if (cond->type >= FLT_COND_SIZE_GREATER) {
447 matched = filter_match_cond
448 (cond, msginfo, hlist, fltinfo);
449 if (matched == TRUE)
450 return TRUE;
451 }
452 }
453
454 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
455 cond = (FilterCond *)cur->data;
456 if (cond->type <= FLT_COND_TO_OR_CC) {
457 matched = filter_match_cond
458 (cond, msginfo, hlist, fltinfo);
459 if (matched == TRUE)
460 return TRUE;
461 }
462 }
463
464 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
465 cond = (FilterCond *)cur->data;
466 if (cond->type == FLT_COND_BODY ||
467 cond->type == FLT_COND_CMD_TEST) {
468 matched = filter_match_cond
469 (cond, msginfo, hlist, fltinfo);
470 if (matched == TRUE)
471 return TRUE;
472 }
473 }
474
475 return FALSE;
476 }
477
478 return FALSE;
479 }
480
filter_match_cond(FilterCond * cond,MsgInfo * msginfo,GSList * hlist,FilterInfo * fltinfo)481 static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo,
482 GSList *hlist, FilterInfo *fltinfo)
483 {
484 gint ret;
485 gboolean matched = FALSE;
486 gboolean not_match = FALSE;
487 gint64 timediff = 0;
488 gchar *file;
489 gchar *cmdline;
490 PrefsAccount *cond_ac;
491
492 switch (cond->type) {
493 case FLT_COND_HEADER:
494 if (cond->match_type == FLT_IN_ADDRESSBOOK)
495 return filter_match_in_addressbook(cond, hlist, fltinfo);
496 else
497 return filter_match_header_cond(cond, hlist);
498 case FLT_COND_ANY_HEADER:
499 return filter_match_header_cond(cond, hlist);
500 case FLT_COND_TO_OR_CC:
501 if (cond->match_type == FLT_IN_ADDRESSBOOK)
502 return filter_match_in_addressbook(cond, hlist, fltinfo);
503 else
504 return filter_match_header_cond(cond, hlist);
505 case FLT_COND_BODY:
506 matched = procmime_find_string(msginfo, cond->str_value,
507 cond->match_func);
508 break;
509 case FLT_COND_CMD_TEST:
510 file = procmsg_get_message_file(msginfo);
511 if (!file)
512 return FALSE;
513 cmdline = g_strconcat(cond->str_value, " \"", file, "\"", NULL);
514 ret = execute_command_line_async_wait(cmdline);
515 fltinfo->last_exec_exit_status = ret;
516 matched = (ret == 0);
517 if (ret == -1)
518 fltinfo->error = FLT_ERROR_EXEC_FAILED;
519 g_free(cmdline);
520 g_free(file);
521 break;
522 case FLT_COND_SIZE_GREATER:
523 matched = (msginfo->size > cond->int_value * 1024);
524 break;
525 case FLT_COND_AGE_GREATER:
526 timediff = time(NULL) - msginfo->date_t;
527 matched = (timediff > cond->int_value * 24 * 60 * 60);
528 break;
529 case FLT_COND_UNREAD:
530 matched = MSG_IS_UNREAD(msginfo->flags);
531 break;
532 case FLT_COND_MARK:
533 matched = MSG_IS_MARKED(msginfo->flags);
534 break;
535 case FLT_COND_COLOR_LABEL:
536 matched = (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != 0);
537 break;
538 case FLT_COND_MIME:
539 matched = MSG_IS_MIME(msginfo->flags);
540 break;
541 case FLT_COND_ACCOUNT:
542 cond_ac = account_find_from_id(cond->int_value);
543 matched = (cond_ac != NULL && cond_ac == fltinfo->account);
544 break;
545 default:
546 g_warning("filter_match_cond(): unknown condition: %d\n",
547 cond->type);
548 fltinfo->error = FLT_ERROR_ERROR;
549 return FALSE;
550 }
551
552 if (FLT_IS_NOT_MATCH(cond->match_flag)) {
553 not_match = TRUE;
554 matched = !matched;
555 }
556
557 if (matched && get_debug_mode()) {
558 gchar *sv = cond->str_value ? cond->str_value : "";
559 gchar *nm = not_match ? " (reverse match)" : "";
560
561 switch (cond->type) {
562 case FLT_COND_BODY:
563 debug_print("filter-log: %s: BODY, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
564 break;
565 case FLT_COND_CMD_TEST:
566 debug_print("filter-log: %s: CMD_TEST, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
567 break;
568 case FLT_COND_SIZE_GREATER:
569 debug_print("filter-log: %s: SIZE_GREATER: %u %s %d (KB)%s\n", G_STRFUNC, msginfo->size, not_match ? "<=" : ">", cond->int_value, nm);
570 break;
571 case FLT_COND_AGE_GREATER:
572 debug_print("filter-log: %s: AGE_GREATER: %lld (sec) %s %d (day)%s\n", G_STRFUNC, timediff, not_match ? "<=" : ">", cond->int_value, nm);
573 break;
574 case FLT_COND_UNREAD:
575 debug_print("filter-log: %s: UNREAD%s\n", G_STRFUNC, nm);
576 break;
577 case FLT_COND_MARK:
578 debug_print("filter-log: %s: MARK%s\n", G_STRFUNC, nm);
579 break;
580 case FLT_COND_COLOR_LABEL:
581 debug_print("filter-log: %s: COLOR_LABEL%s\n", G_STRFUNC, nm);
582 break;
583 case FLT_COND_MIME:
584 debug_print("filter-log: %s: MIME%s\n", G_STRFUNC, nm);
585 break;
586 case FLT_COND_ACCOUNT:
587 debug_print("filter-log: %s: ACCOUNT [%d]%s\n", G_STRFUNC, cond->int_value, nm);
588 break;
589 default:
590 break;
591 }
592 }
593
594 return matched;
595 }
596
filter_match_header_cond(FilterCond * cond,GSList * hlist)597 static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist)
598 {
599 gboolean matched = FALSE;
600 gboolean not_match = FALSE;
601 GSList *cur;
602 Header *header;
603
604 for (cur = hlist; cur != NULL; cur = cur->next) {
605 header = (Header *)cur->data;
606
607 switch (cond->type) {
608 case FLT_COND_HEADER:
609 if (!g_ascii_strcasecmp
610 (header->name, cond->header_name)) {
611 if (!cond->str_value ||
612 cond->match_func(header->body,
613 cond->str_value))
614 matched = TRUE;
615 }
616 break;
617 case FLT_COND_ANY_HEADER:
618 if (!cond->str_value ||
619 cond->match_func(header->body, cond->str_value))
620 matched = TRUE;
621 break;
622 case FLT_COND_TO_OR_CC:
623 if (!g_ascii_strcasecmp(header->name, "To") ||
624 !g_ascii_strcasecmp(header->name, "Cc")) {
625 if (!cond->str_value ||
626 cond->match_func(header->body,
627 cond->str_value))
628 matched = TRUE;
629 }
630 break;
631 default:
632 break;
633 }
634
635 if (matched == TRUE)
636 break;
637 }
638
639 if (FLT_IS_NOT_MATCH(cond->match_flag)) {
640 not_match = TRUE;
641 matched = !matched;
642 }
643
644 if (matched && get_debug_mode()) {
645 gchar *sv = cond->str_value ? cond->str_value : "";
646 gchar *nm = not_match ? " (reverse match)" : "";
647
648 switch (cond->type) {
649 case FLT_COND_HEADER:
650 debug_print("filter-log: %s: HEADER [%s], str_value: [%s]%s\n", G_STRFUNC, cond->header_name, sv, nm);
651 break;
652 case FLT_COND_ANY_HEADER:
653 debug_print("filter-log: %s: ANY_HEADER, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
654 break;
655 case FLT_COND_TO_OR_CC:
656 debug_print("filter-log: %s: TO_OR_CC, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
657 break;
658 default:
659 break;
660 }
661 }
662
663 return matched;
664 }
665
filter_match_in_addressbook(FilterCond * cond,GSList * hlist,FilterInfo * fltinfo)666 static gboolean filter_match_in_addressbook(FilterCond *cond, GSList *hlist,
667 FilterInfo *fltinfo)
668 {
669 gboolean matched = FALSE;
670 gboolean not_match = FALSE;
671 GSList *cur;
672 Header *header;
673
674 if (!default_addrbook_func)
675 return FALSE;
676 if (cond->type != FLT_COND_HEADER && cond->type != FLT_COND_TO_OR_CC)
677 return FALSE;
678
679 for (cur = hlist; cur != NULL; cur = cur->next) {
680 header = (Header *)cur->data;
681
682 if (cond->type == FLT_COND_HEADER) {
683 if (!g_ascii_strcasecmp
684 (header->name, cond->header_name)) {
685 if (default_addrbook_func(header->body))
686 matched = TRUE;
687 }
688 } else if (cond->type == FLT_COND_TO_OR_CC) {
689 if (!g_ascii_strcasecmp(header->name, "To") ||
690 !g_ascii_strcasecmp(header->name, "Cc")) {
691 if (default_addrbook_func(header->body))
692 matched = TRUE;
693 }
694 }
695
696 if (matched == TRUE)
697 break;
698 }
699
700 if (FLT_IS_NOT_MATCH(cond->match_flag)) {
701 not_match = TRUE;
702 matched = !matched;
703 }
704
705 if (matched && get_debug_mode()) {
706 gchar *nm = not_match ? " (reverse match)" : "";
707
708 switch (cond->type) {
709 case FLT_COND_HEADER:
710 debug_print("filter-log: %s: HEADER [%s], IN_ADDRESSBOOK%s\n", G_STRFUNC, cond->header_name, nm);
711 break;
712 case FLT_COND_TO_OR_CC:
713 debug_print("filter-log: %s: TO_OR_CC, IN_ADDRESSBOOK%s\n", G_STRFUNC, nm);
714 break;
715 default:
716 break;
717 }
718 }
719
720 return matched;
721 }
722
filter_rule_requires_full_headers(FilterRule * rule)723 gboolean filter_rule_requires_full_headers(FilterRule *rule)
724 {
725 GSList *cur;
726
727 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
728 FilterCond *cond = (FilterCond *)cur->data;
729 const gchar *name = cond->header_name;
730
731 if (cond->type == FLT_COND_HEADER && name) {
732 if (g_ascii_strcasecmp(name, "Date") != 0 &&
733 g_ascii_strcasecmp(name, "From") != 0 &&
734 g_ascii_strcasecmp(name, "To") != 0 &&
735 g_ascii_strcasecmp(name, "Newsgroups") != 0 &&
736 g_ascii_strcasecmp(name, "Subject") != 0)
737 return TRUE;
738 } else if (cond->type == FLT_COND_ANY_HEADER ||
739 cond->type == FLT_COND_TO_OR_CC)
740 return TRUE;
741 }
742
743 return FALSE;
744 }
745
746 #define RETURN_IF_TAG_NOT_MATCH(tag_name) \
747 if (strcmp2(xmlnode->tag->tag, tag_name) != 0) { \
748 g_warning("tag name != \"" tag_name "\"\n"); \
749 filter_cond_list_free(cond_list); \
750 filter_action_list_free(cond_list); \
751 return FALSE; \
752 } \
753
754 #define STR_SWITCH(sw) { const gchar *sw_str = sw;
755 #define STR_CASE_BEGIN(str) if (!strcmp(sw_str, str)) {
756 #define STR_CASE(str) } else if (!strcmp(sw_str, str)) {
757 #define STR_CASE_END } }
758
filter_xml_node_func(GNode * node,gpointer data)759 static gboolean filter_xml_node_func(GNode *node, gpointer data)
760 {
761 XMLNode *xmlnode;
762 GList *list;
763 const gchar *name = NULL;
764 FilterTiming timing = FLT_TIMING_ANY;
765 gboolean enabled = TRUE;
766 FilterRule *rule;
767 FilterBoolOp bool_op = FLT_OR;
768 const gchar *target_folder = NULL;
769 gboolean recursive = FALSE;
770 GSList *cond_list = NULL;
771 GSList *action_list = NULL;
772 GNode *child, *cond_child, *action_child;
773 GSList **fltlist = (GSList **)data;
774
775 if (g_node_depth(node) != 2) return FALSE;
776 g_return_val_if_fail(node->data != NULL, FALSE);
777
778 xmlnode = node->data;
779 RETURN_IF_TAG_NOT_MATCH("rule");
780
781 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
782 XMLAttr *attr = (XMLAttr *)list->data;
783
784 if (!attr || !attr->name || !attr->value) continue;
785 if (!strcmp(attr->name, "name"))
786 name = attr->value;
787 else if (!strcmp(attr->name, "timing")) {
788 if (!strcmp(attr->value, "any"))
789 timing = FLT_TIMING_ANY;
790 else if (!strcmp(attr->value, "receive"))
791 timing = FLT_TIMING_ON_RECEIVE;
792 else if (!strcmp(attr->value, "manual"))
793 timing = FLT_TIMING_MANUAL;
794 } else if (!strcmp(attr->name, "enabled")) {
795 if (!strcmp(attr->value, "true"))
796 enabled = TRUE;
797 else
798 enabled = FALSE;
799 }
800 }
801
802 /* condition list */
803 child = node->children;
804 if (!child) {
805 g_warning("<rule> must have children\n");
806 return FALSE;
807 }
808 xmlnode = child->data;
809 RETURN_IF_TAG_NOT_MATCH("condition-list");
810 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
811 XMLAttr *attr = (XMLAttr *)list->data;
812
813 if (attr && attr->name && attr->value &&
814 !strcmp(attr->name, "bool")) {
815 if (!strcmp(attr->value, "or"))
816 bool_op = FLT_OR;
817 else
818 bool_op = FLT_AND;
819 }
820 }
821
822 for (cond_child = child->children; cond_child != NULL;
823 cond_child = cond_child->next) {
824 const gchar *type = NULL;
825 const gchar *name = NULL;
826 const gchar *value = NULL;
827 gboolean case_sens = FALSE;
828 FilterCond *cond;
829 FilterCondType cond_type = FLT_COND_HEADER;
830 FilterMatchType match_type = FLT_CONTAIN;
831 FilterMatchFlag match_flag = 0;
832
833 xmlnode = cond_child->data;
834 if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
835
836 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
837 XMLAttr *attr = (XMLAttr *)list->data;
838
839 if (!attr || !attr->name || !attr->value) continue;
840
841 STR_SWITCH(attr->name)
842 STR_CASE_BEGIN("type")
843 type = attr->value;
844 STR_CASE("name")
845 name = attr->value;
846 STR_CASE("case")
847 case_sens = TRUE;
848 STR_CASE("recursive")
849 if (!strcmp(attr->value, "true"))
850 recursive = TRUE;
851 else
852 recursive = FALSE;
853 STR_CASE_END
854 }
855
856 if (type) {
857 filter_rule_match_type_str_to_enum
858 (type, &match_type, &match_flag);
859 }
860 if (case_sens)
861 match_flag |= FLT_CASE_SENS;
862 value = xmlnode->element;
863
864 STR_SWITCH(xmlnode->tag->tag)
865 STR_CASE_BEGIN("match-header")
866 cond_type = FLT_COND_HEADER;
867 STR_CASE("match-any-header")
868 cond_type = FLT_COND_ANY_HEADER;
869 STR_CASE("match-to-or-cc")
870 cond_type = FLT_COND_TO_OR_CC;
871 STR_CASE("match-body-text")
872 cond_type = FLT_COND_BODY;
873 STR_CASE("command-test")
874 cond_type = FLT_COND_CMD_TEST;
875 STR_CASE("size")
876 cond_type = FLT_COND_SIZE_GREATER;
877 STR_CASE("age")
878 cond_type = FLT_COND_AGE_GREATER;
879 STR_CASE("unread")
880 cond_type = FLT_COND_UNREAD;
881 STR_CASE("mark")
882 cond_type = FLT_COND_MARK;
883 STR_CASE("color-label")
884 cond_type = FLT_COND_COLOR_LABEL;
885 STR_CASE("mime")
886 cond_type = FLT_COND_MIME;
887 STR_CASE("account-id")
888 cond_type = FLT_COND_ACCOUNT;
889 STR_CASE("target-folder")
890 target_folder = value;
891 continue;
892 STR_CASE_END
893
894 cond = filter_cond_new(cond_type, match_type, match_flag,
895 name, value);
896 cond_list = g_slist_append(cond_list, cond);
897 }
898
899 /* action list */
900 child = child->next;
901 if (!child) {
902 g_warning("<rule> must have multiple children\n");
903 filter_cond_list_free(cond_list);
904 return FALSE;
905 }
906 xmlnode = child->data;
907 RETURN_IF_TAG_NOT_MATCH("action-list");
908
909 for (action_child = child->children; action_child != NULL;
910 action_child = action_child->next) {
911 FilterAction *action;
912 FilterActionType action_type = FLT_ACTION_NONE;
913
914 xmlnode = action_child->data;
915 if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
916
917 STR_SWITCH(xmlnode->tag->tag)
918 STR_CASE_BEGIN("move")
919 action_type = FLT_ACTION_MOVE;
920 STR_CASE("copy")
921 action_type = FLT_ACTION_COPY;
922 STR_CASE("not-receive")
923 action_type = FLT_ACTION_NOT_RECEIVE;
924 STR_CASE("delete")
925 action_type = FLT_ACTION_DELETE;
926 STR_CASE("exec")
927 action_type = FLT_ACTION_EXEC;
928 STR_CASE("exec-async")
929 action_type = FLT_ACTION_EXEC_ASYNC;
930 STR_CASE("mark")
931 action_type = FLT_ACTION_MARK;
932 STR_CASE("color-label")
933 action_type = FLT_ACTION_COLOR_LABEL;
934 STR_CASE("mark-as-read")
935 action_type = FLT_ACTION_MARK_READ;
936 STR_CASE("forward")
937 action_type = FLT_ACTION_FORWARD;
938 STR_CASE("forward-as-attachment")
939 action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT;
940 STR_CASE("redirect")
941 action_type = FLT_ACTION_REDIRECT;
942 STR_CASE("stop-eval")
943 action_type = FLT_ACTION_STOP_EVAL;
944 STR_CASE_END
945
946 action = filter_action_new(action_type, xmlnode->element);
947 action_list = g_slist_append(action_list, action);
948 }
949
950 if (name && cond_list) {
951 rule = filter_rule_new(name, bool_op, cond_list, action_list);
952 rule->timing = timing;
953 rule->enabled = enabled;
954 if (target_folder) {
955 rule->target_folder = g_strdup(target_folder);
956 rule->recursive = recursive;
957 }
958 *fltlist = g_slist_prepend(*fltlist, rule);
959 }
960
961 return FALSE;
962 }
963
964 #undef RETURN_IF_TAG_NOT_MATCH
965 #undef STR_SWITCH
966 #undef STR_CASE_BEGIN
967 #undef STR_CASE
968 #undef STR_CASE_END
969
filter_xml_node_to_filter_list(GNode * node)970 GSList *filter_xml_node_to_filter_list(GNode *node)
971 {
972 GSList *fltlist = NULL;
973
974 g_return_val_if_fail(node != NULL, NULL);
975
976 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
977 filter_xml_node_func, &fltlist);
978 fltlist = g_slist_reverse(fltlist);
979
980 return fltlist;
981 }
982
filter_read_file(const gchar * file)983 GSList *filter_read_file(const gchar *file)
984 {
985 GNode *node;
986 GSList *list;
987
988 g_return_val_if_fail(file != NULL, NULL);
989
990 debug_print("Reading %s\n", file);
991
992 if (!is_file_exist(file))
993 return NULL;
994
995 node = xml_parse_file(file);
996 if (!node) {
997 g_warning("Can't parse %s\n", file);
998 return NULL;
999 }
1000
1001 list = filter_xml_node_to_filter_list(node);
1002
1003 xml_free_tree(node);
1004
1005 return list;
1006 }
1007
filter_read_config(void)1008 void filter_read_config(void)
1009 {
1010 gchar *rcpath;
1011
1012 debug_print("Reading filter configuration...\n");
1013
1014 /* remove all previous filter list */
1015 while (prefs_common.fltlist != NULL) {
1016 FilterRule *rule = (FilterRule *)prefs_common.fltlist->data;
1017
1018 filter_rule_free(rule);
1019 prefs_common.fltlist = g_slist_remove(prefs_common.fltlist,
1020 rule);
1021 }
1022
1023 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1024 NULL);
1025 prefs_common.fltlist = filter_read_file(rcpath);
1026 g_free(rcpath);
1027 }
1028
1029 #define NODE_NEW(tag, text) \
1030 node = xml_node_new(xml_tag_new(tag), text)
1031 #define ADD_ATTR(name, value) \
1032 xml_tag_add_attr(node->tag, xml_attr_new(name, value))
1033
filter_write_file(GSList * list,const gchar * file)1034 void filter_write_file(GSList *list, const gchar *file)
1035 {
1036 PrefFile *pfile;
1037 GSList *cur;
1038
1039 g_return_if_fail(file != NULL);
1040
1041 if ((pfile = prefs_file_open(file)) == NULL) {
1042 g_warning("failed to write filter configuration to file: %s\n",
1043 file);
1044 return;
1045 }
1046
1047 xml_file_put_xml_decl(pfile->fp);
1048 fputs("\n<filter>\n", pfile->fp);
1049
1050 for (cur = list; cur != NULL; cur = cur->next) {
1051 FilterRule *rule = (FilterRule *)cur->data;
1052 GSList *cur_cond;
1053 GSList *cur_action;
1054 gchar match_type[64];
1055 gchar nstr[16];
1056
1057 fputs(" <rule name=\"", pfile->fp);
1058 xml_file_put_escape_str(pfile->fp, rule->name);
1059 fprintf(pfile->fp, "\" timing=\"%s\"",
1060 rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" :
1061 rule->timing == FLT_TIMING_MANUAL ? "manual" : "any");
1062 fprintf(pfile->fp, " enabled=\"%s\">\n",
1063 rule->enabled ? "true" : "false");
1064
1065 fprintf(pfile->fp, " <condition-list bool=\"%s\">\n",
1066 rule->bool_op == FLT_OR ? "or" : "and");
1067
1068 for (cur_cond = rule->cond_list; cur_cond != NULL;
1069 cur_cond = cur_cond->next) {
1070 FilterCond *cond = (FilterCond *)cur_cond->data;
1071 XMLNode *node = NULL;
1072
1073 switch (cond->match_type) {
1074 case FLT_CONTAIN:
1075 strncpy2(match_type,
1076 FLT_IS_NOT_MATCH(cond->match_flag)
1077 ? "not-contain" : "contains",
1078 sizeof(match_type));
1079 break;
1080 case FLT_EQUAL:
1081 strncpy2(match_type,
1082 FLT_IS_NOT_MATCH(cond->match_flag)
1083 ? "is-not" : "is", sizeof(match_type));
1084 break;
1085 case FLT_REGEX:
1086 strncpy2(match_type,
1087 FLT_IS_NOT_MATCH(cond->match_flag)
1088 ? "not-regex" : "regex",
1089 sizeof(match_type));
1090 break;
1091 case FLT_IN_ADDRESSBOOK:
1092 strncpy2(match_type,
1093 FLT_IS_NOT_MATCH(cond->match_flag)
1094 ? "not-in-addressbook" : "in-addressbook",
1095 sizeof(match_type));
1096 break;
1097 default:
1098 match_type[0] = '\0';
1099 break;
1100 }
1101
1102 switch (cond->type) {
1103 case FLT_COND_HEADER:
1104 NODE_NEW("match-header", cond->str_value);
1105 ADD_ATTR("type", match_type);
1106 ADD_ATTR("name", cond->header_name);
1107 if (FLT_IS_CASE_SENS(cond->match_flag))
1108 ADD_ATTR("case", "true");
1109 break;
1110 case FLT_COND_ANY_HEADER:
1111 NODE_NEW("match-any-header", cond->str_value);
1112 ADD_ATTR("type", match_type);
1113 if (FLT_IS_CASE_SENS(cond->match_flag))
1114 ADD_ATTR("case", "true");
1115 break;
1116 case FLT_COND_TO_OR_CC:
1117 NODE_NEW("match-to-or-cc", cond->str_value);
1118 ADD_ATTR("type", match_type);
1119 if (FLT_IS_CASE_SENS(cond->match_flag))
1120 ADD_ATTR("case", "true");
1121 break;
1122 case FLT_COND_BODY:
1123 NODE_NEW("match-body-text", cond->str_value);
1124 ADD_ATTR("type", match_type);
1125 if (FLT_IS_CASE_SENS(cond->match_flag))
1126 ADD_ATTR("case", "true");
1127 break;
1128 case FLT_COND_CMD_TEST:
1129 NODE_NEW("command-test", cond->str_value);
1130 break;
1131 case FLT_COND_SIZE_GREATER:
1132 NODE_NEW("size", itos_buf(nstr, cond->int_value));
1133 ADD_ATTR("type",
1134 FLT_IS_NOT_MATCH(cond->match_flag)
1135 ? "lt" : "gt");
1136 break;
1137 case FLT_COND_AGE_GREATER:
1138 NODE_NEW("age", itos_buf(nstr, cond->int_value));
1139 ADD_ATTR("type",
1140 FLT_IS_NOT_MATCH(cond->match_flag)
1141 ? "lt" : "gt");
1142 break;
1143 case FLT_COND_UNREAD:
1144 NODE_NEW("unread", NULL);
1145 ADD_ATTR("type",
1146 FLT_IS_NOT_MATCH(cond->match_flag)
1147 ? "is-not" : "is");
1148 break;
1149 case FLT_COND_MARK:
1150 NODE_NEW("mark", NULL);
1151 ADD_ATTR("type",
1152 FLT_IS_NOT_MATCH(cond->match_flag)
1153 ? "is-not" : "is");
1154 break;
1155 case FLT_COND_COLOR_LABEL:
1156 NODE_NEW("color-label", NULL);
1157 ADD_ATTR("type",
1158 FLT_IS_NOT_MATCH(cond->match_flag)
1159 ? "is-not" : "is");
1160 break;
1161 case FLT_COND_MIME:
1162 NODE_NEW("mime", NULL);
1163 ADD_ATTR("type",
1164 FLT_IS_NOT_MATCH(cond->match_flag)
1165 ? "is-not" : "is");
1166 break;
1167 case FLT_COND_ACCOUNT:
1168 NODE_NEW("account-id", itos_buf(nstr, cond->int_value));
1169 break;
1170 default:
1171 break;
1172 }
1173
1174 if (node) {
1175 fputs(" ", pfile->fp);
1176 xml_file_put_node(pfile->fp, node);
1177 xml_free_node(node);
1178 }
1179 }
1180
1181 if (rule->target_folder) {
1182 XMLNode *node;
1183
1184 NODE_NEW("target-folder", rule->target_folder);
1185 ADD_ATTR("recursive", rule->recursive
1186 ? "true" : "false");
1187 fputs(" ", pfile->fp);
1188 xml_file_put_node(pfile->fp, node);
1189 xml_free_node(node);
1190 }
1191
1192 fputs(" </condition-list>\n", pfile->fp);
1193
1194 fputs(" <action-list>\n", pfile->fp);
1195
1196 for (cur_action = rule->action_list; cur_action != NULL;
1197 cur_action = cur_action->next) {
1198 FilterAction *action = (FilterAction *)cur_action->data;
1199 XMLNode *node = NULL;
1200
1201 switch (action->type) {
1202 case FLT_ACTION_MOVE:
1203 NODE_NEW("move", action->str_value);
1204 break;
1205 case FLT_ACTION_COPY:
1206 NODE_NEW("copy", action->str_value);
1207 break;
1208 case FLT_ACTION_NOT_RECEIVE:
1209 NODE_NEW("not-receive", NULL);
1210 break;
1211 case FLT_ACTION_DELETE:
1212 NODE_NEW("delete", NULL);
1213 break;
1214 case FLT_ACTION_EXEC:
1215 NODE_NEW("exec", action->str_value);
1216 break;
1217 case FLT_ACTION_EXEC_ASYNC:
1218 NODE_NEW("exec-async", action->str_value);
1219 break;
1220 case FLT_ACTION_MARK:
1221 NODE_NEW("mark", NULL);
1222 break;
1223 case FLT_ACTION_COLOR_LABEL:
1224 NODE_NEW("color-label", action->str_value);
1225 break;
1226 case FLT_ACTION_MARK_READ:
1227 NODE_NEW("mark-as-read", NULL);
1228 break;
1229 case FLT_ACTION_FORWARD:
1230 NODE_NEW("forward", action->str_value);
1231 break;
1232 case FLT_ACTION_FORWARD_AS_ATTACHMENT:
1233 NODE_NEW("forward-as-attachment",
1234 action->str_value);
1235 break;
1236 case FLT_ACTION_REDIRECT:
1237 NODE_NEW("redirect", action->str_value);
1238 break;
1239 case FLT_ACTION_STOP_EVAL:
1240 NODE_NEW("stop-eval", NULL);
1241 break;
1242 default:
1243 break;
1244 }
1245
1246 if (node) {
1247 fputs(" ", pfile->fp);
1248 xml_file_put_node(pfile->fp, node);
1249 xml_free_node(node);
1250 }
1251 }
1252
1253 fputs(" </action-list>\n", pfile->fp);
1254
1255 fputs(" </rule>\n", pfile->fp);
1256 }
1257
1258 fputs("</filter>\n", pfile->fp);
1259
1260 if (prefs_file_close(pfile) < 0) {
1261 g_warning("failed to write filter configuration to file: %s\n",
1262 file);
1263 return;
1264 }
1265 }
1266
filter_write_config(void)1267 void filter_write_config(void)
1268 {
1269 gchar *rcpath;
1270
1271 debug_print("Writing filter configuration...\n");
1272
1273 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1274 NULL);
1275 filter_write_file(prefs_common.fltlist, rcpath);
1276 g_free(rcpath);
1277 }
1278
1279 #undef NODE_NEW
1280 #undef ADD_ATTR
1281
filter_get_str(FilterRule * rule)1282 gchar *filter_get_str(FilterRule *rule)
1283 {
1284 gchar *str;
1285 FilterCond *cond1, *cond2;
1286 FilterAction *action = NULL;
1287 GSList *cur;
1288 gint flag1 = 0, flag2 = 0;
1289
1290 cond1 = (FilterCond *)rule->cond_list->data;
1291 if (rule->cond_list->next)
1292 cond2 = (FilterCond *)rule->cond_list->next->data;
1293 else
1294 cond2 = NULL;
1295
1296 /* new -> old flag conversion */
1297 switch (cond1->match_type) {
1298 case FLT_CONTAIN:
1299 case FLT_EQUAL:
1300 flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN;
1301 if (FLT_IS_CASE_SENS(cond1->match_flag))
1302 flag1 |= FLT_O_CASE_SENS;
1303 break;
1304 case FLT_REGEX:
1305 flag1 = FLT_O_REGEX; break;
1306 default:
1307 break;
1308 }
1309 if (cond2) {
1310 switch (cond2->match_type) {
1311 case FLT_CONTAIN:
1312 case FLT_EQUAL:
1313 flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN;
1314 if (FLT_IS_CASE_SENS(cond2->match_flag))
1315 flag2 |= FLT_O_CASE_SENS;
1316 break;
1317 case FLT_REGEX:
1318 flag2 = FLT_O_REGEX; break;
1319 default:
1320 break;
1321 }
1322 } else
1323 flag2 = FLT_O_CONTAIN;
1324
1325 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1326 action = (FilterAction *)cur->data;
1327 if (action->type == FLT_ACTION_MOVE ||
1328 action->type == FLT_ACTION_NOT_RECEIVE ||
1329 action->type == FLT_ACTION_DELETE)
1330 break;
1331 }
1332
1333 str = g_strdup_printf
1334 ("%s:%s:%c:%s:%s:%s:%d:%d:%c",
1335 cond1->header_name, cond1->str_value ? cond1->str_value : "",
1336 (cond2 && cond2->header_name) ?
1337 (rule->bool_op == FLT_AND ? '&' : '|') : ' ',
1338 (cond2 && cond2->header_name) ? cond2->header_name : "",
1339 (cond2 && cond2->str_value) ? cond2->str_value : "",
1340 (action && action->str_value) ? action->str_value : "",
1341 flag1, flag2,
1342 (action && action->type == FLT_ACTION_MOVE) ? 'm' :
1343 (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' :
1344 (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' ');
1345
1346 return str;
1347 }
1348
1349 #define PARSE_ONE_PARAM(p, srcp) \
1350 { \
1351 p = strchr(srcp, '\t'); \
1352 if (!p) return NULL; \
1353 else \
1354 *p++ = '\0'; \
1355 }
1356
filter_read_str(const gchar * str)1357 FilterRule *filter_read_str(const gchar *str)
1358 {
1359 FilterRule *rule;
1360 FilterBoolOp bool_op;
1361 gint flag;
1362 FilterCond *cond;
1363 FilterMatchType match_type;
1364 FilterMatchFlag match_flag;
1365 FilterAction *action;
1366 GSList *cond_list = NULL;
1367 GSList *action_list = NULL;
1368 gchar *tmp;
1369 gchar *rule_name;
1370 gchar *name1, *body1, *op, *name2, *body2, *dest;
1371 gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL;
1372
1373 Xstrdup_a(tmp, str, return NULL);
1374
1375 name1 = tmp;
1376 PARSE_ONE_PARAM(body1, name1);
1377 PARSE_ONE_PARAM(op, body1);
1378 PARSE_ONE_PARAM(name2, op);
1379 PARSE_ONE_PARAM(body2, name2);
1380 PARSE_ONE_PARAM(dest, body2);
1381 if (strchr(dest, '\t')) {
1382 gchar *p;
1383
1384 PARSE_ONE_PARAM(flag1, dest);
1385 PARSE_ONE_PARAM(flag2, flag1);
1386 PARSE_ONE_PARAM(action1, flag2);
1387 if ((p = strchr(action1, '\t'))) *p = '\0';
1388 }
1389
1390 bool_op = (*op == '&') ? FLT_AND : FLT_OR;
1391
1392 if (*name1) {
1393 if (flag1)
1394 flag = (FilterOldFlag)strtoul(flag1, NULL, 10);
1395 else
1396 flag = FLT_O_CONTAIN;
1397 match_type = FLT_CONTAIN;
1398 match_flag = 0;
1399 if (flag & FLT_O_REGEX)
1400 match_type = FLT_REGEX;
1401 else if (!(flag & FLT_O_CONTAIN))
1402 match_flag = FLT_NOT_MATCH;
1403 if (flag & FLT_O_CASE_SENS)
1404 match_flag |= FLT_CASE_SENS;
1405 cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1406 name1, body1);
1407 cond_list = g_slist_append(cond_list, cond);
1408 }
1409 if (*name2) {
1410 if (flag2)
1411 flag = (FilterOldFlag)strtoul(flag2, NULL, 10);
1412 else
1413 flag = FLT_O_CONTAIN;
1414 match_type = FLT_CONTAIN;
1415 match_flag = 0;
1416 if (flag & FLT_O_REGEX)
1417 match_type = FLT_REGEX;
1418 else if (!(flag & FLT_O_CONTAIN))
1419 match_flag = FLT_NOT_MATCH;
1420 if (flag & FLT_O_CASE_SENS)
1421 match_flag |= FLT_CASE_SENS;
1422 cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1423 name2, body2);
1424 cond_list = g_slist_append(cond_list, cond);
1425 }
1426
1427 action = filter_action_new(FLT_ACTION_MOVE,
1428 *dest ? g_strdup(dest) : NULL);
1429 if (action1) {
1430 switch (*action1) {
1431 case 'm': action->type = FLT_ACTION_MOVE; break;
1432 case 'n': action->type = FLT_ACTION_NOT_RECEIVE; break;
1433 case 'd': action->type = FLT_ACTION_DELETE; break;
1434 default: g_warning("Invalid action: `%c'\n", *action1);
1435 }
1436 }
1437 action_list = g_slist_append(action_list, action);
1438
1439 Xstrdup_a(rule_name, str, return NULL);
1440 subst_char(rule_name, '\t', ':');
1441
1442 rule = filter_rule_new(rule_name, bool_op, cond_list, action_list);
1443
1444 return rule;
1445 }
1446
filter_set_addressbook_func(FilterInAddressBookFunc func)1447 void filter_set_addressbook_func(FilterInAddressBookFunc func)
1448 {
1449 default_addrbook_func = func;
1450 }
1451
filter_get_addressbook_func(void)1452 FilterInAddressBookFunc filter_get_addressbook_func(void)
1453 {
1454 return default_addrbook_func;
1455 }
1456
filter_rule_new(const gchar * name,FilterBoolOp bool_op,GSList * cond_list,GSList * action_list)1457 FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op,
1458 GSList *cond_list, GSList *action_list)
1459 {
1460 FilterRule *rule;
1461
1462 rule = g_new0(FilterRule, 1);
1463 rule->name = g_strdup(name);
1464 rule->bool_op = bool_op;
1465 rule->cond_list = cond_list;
1466 rule->action_list = action_list;
1467 rule->timing = FLT_TIMING_ANY;
1468 rule->enabled = TRUE;
1469
1470 return rule;
1471 }
1472
filter_cond_new(FilterCondType type,FilterMatchType match_type,FilterMatchFlag match_flag,const gchar * header,const gchar * value)1473 FilterCond *filter_cond_new(FilterCondType type,
1474 FilterMatchType match_type,
1475 FilterMatchFlag match_flag,
1476 const gchar *header, const gchar *value)
1477 {
1478 FilterCond *cond;
1479
1480 cond = g_new0(FilterCond, 1);
1481 cond->type = type;
1482 cond->match_type = match_type;
1483 cond->match_flag = match_flag;
1484
1485 if (type == FLT_COND_HEADER)
1486 cond->header_name =
1487 (header && *header) ? g_strdup(header) : NULL;
1488 else
1489 cond->header_name = NULL;
1490
1491 cond->str_value = (value && *value) ? g_strdup(value) : NULL;
1492 if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER ||
1493 type == FLT_COND_ACCOUNT)
1494 cond->int_value = atoi(value);
1495 else
1496 cond->int_value = 0;
1497
1498 if (match_type == FLT_REGEX)
1499 cond->match_func = strmatch_regex;
1500 else if (match_type == FLT_EQUAL) {
1501 if (FLT_IS_CASE_SENS(match_flag))
1502 cond->match_func = str_find_equal;
1503 else
1504 cond->match_func = str_case_find_equal;
1505 } else if (match_type == FLT_IN_ADDRESSBOOK) {
1506 cond->match_func = str_case_find_equal;
1507 } else {
1508 if (FLT_IS_CASE_SENS(match_flag))
1509 cond->match_func = str_find;
1510 else
1511 cond->match_func = str_case_find;
1512 }
1513
1514 return cond;
1515 }
1516
filter_action_new(FilterActionType type,const gchar * str)1517 FilterAction *filter_action_new(FilterActionType type, const gchar *str)
1518 {
1519 FilterAction *action;
1520
1521 action = g_new0(FilterAction, 1);
1522 action->type = type;
1523
1524 action->str_value = (str && *str) ? g_strdup(str) : NULL;
1525 if (type == FLT_ACTION_COLOR_LABEL && str)
1526 action->int_value = atoi(str);
1527 else
1528 action->int_value = 0;
1529
1530 return action;
1531 }
1532
filter_info_new(void)1533 FilterInfo *filter_info_new(void)
1534 {
1535 FilterInfo *fltinfo;
1536
1537 fltinfo = g_new0(FilterInfo, 1);
1538 fltinfo->dest_list = NULL;
1539 fltinfo->move_dest = NULL;
1540 fltinfo->drop_done = FALSE;
1541 fltinfo->error = FLT_ERROR_OK;
1542 fltinfo->last_exec_exit_status = 0;
1543
1544 return fltinfo;
1545 }
1546
filter_junk_rule_create(PrefsAccount * account,FolderItem * default_junk,gboolean is_manual)1547 FilterRule *filter_junk_rule_create(PrefsAccount *account,
1548 FolderItem *default_junk,
1549 gboolean is_manual)
1550 {
1551 FilterRule *rule;
1552 FilterCond *cond;
1553 FilterAction *action;
1554 GSList *cond_list = NULL, *action_list = NULL;
1555 gchar *junk_id = NULL;
1556 FolderItem *item = NULL;
1557
1558 if (!prefs_common.junk_classify_cmd)
1559 return NULL;
1560
1561 if (prefs_common.junk_folder)
1562 item = folder_find_item_from_identifier(prefs_common.junk_folder);
1563
1564 if (!item && account) {
1565 Folder *folder = NULL;
1566 GList *list;
1567
1568 /* find most suitable Junk folder for account */
1569
1570 if (account->inbox && *account->inbox == '#') {
1571 FolderItem *inbox;
1572 inbox = folder_find_item_from_identifier(account->inbox);
1573 if (inbox) {
1574 folder = inbox->folder;
1575 if (folder)
1576 item = folder_get_junk(folder);
1577 }
1578 }
1579 if (!item) {
1580 folder = FOLDER(account->folder);
1581 if (folder)
1582 item = folder_get_junk(folder);
1583 }
1584 if (!item) {
1585 for (list = folder_get_list(); list != NULL; list = list->next) {
1586 folder = FOLDER(list->data);
1587 if (FOLDER_IS_LOCAL(folder)) {
1588 if (folder->account == account)
1589 item = folder_get_junk(folder);
1590 if (!item && folder->node) {
1591 item = FOLDER_ITEM(folder->node->data);
1592 if (item && item->account == account && item->folder)
1593 item = folder_get_junk(item->folder);
1594 else
1595 item = NULL;
1596 }
1597 }
1598 if (item)
1599 break;
1600 }
1601 }
1602 }
1603
1604 if (!item)
1605 item = default_junk;
1606 if (!item)
1607 item = folder_get_default_junk();
1608 if (!item)
1609 return NULL;
1610 junk_id = folder_item_get_identifier(item);
1611 if (!junk_id)
1612 return NULL;
1613
1614 debug_print("filter_junk_rule_create: junk folder: %s\n",
1615 junk_id);
1616
1617 if (prefs_common.nofilter_junk_sender_in_book) {
1618 cond = filter_cond_new(FLT_COND_HEADER, FLT_IN_ADDRESSBOOK,
1619 FLT_NOT_MATCH, "From", NULL);
1620 cond_list = g_slist_append(cond_list, cond);
1621 }
1622
1623 cond = filter_cond_new(FLT_COND_CMD_TEST, 0, 0, NULL,
1624 prefs_common.junk_classify_cmd);
1625 cond_list = g_slist_append(cond_list, cond);
1626
1627 if (prefs_common.delete_junk_on_recv && !is_manual) {
1628 action = filter_action_new(FLT_ACTION_COPY, junk_id);
1629 action_list = g_slist_append(NULL, action);
1630 action = filter_action_new(FLT_ACTION_DELETE, NULL);
1631 action_list = g_slist_append(action_list, action);
1632 } else {
1633 action = filter_action_new(FLT_ACTION_MOVE, junk_id);
1634 action_list = g_slist_append(NULL, action);
1635 }
1636
1637 if (prefs_common.mark_junk_as_read) {
1638 action = filter_action_new(FLT_ACTION_MARK_READ, NULL);
1639 action_list = g_slist_append(action_list, action);
1640 }
1641
1642 if (is_manual)
1643 rule = filter_rule_new(_("Junk mail filter (manual)"), FLT_AND,
1644 cond_list, action_list);
1645 else
1646 rule = filter_rule_new(_("Junk mail filter"), FLT_AND,
1647 cond_list, action_list);
1648
1649 g_free(junk_id);
1650
1651 return rule;
1652 }
1653
filter_rule_rename_dest_path(FilterRule * rule,const gchar * old_path,const gchar * new_path)1654 void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path,
1655 const gchar *new_path)
1656 {
1657 FilterAction *action;
1658 GSList *cur;
1659 gchar *base;
1660 gchar *dest_path;
1661 gint oldpathlen;
1662
1663 oldpathlen = strlen(old_path);
1664
1665 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1666 action = (FilterAction *)cur->data;
1667
1668 if (action->type != FLT_ACTION_MOVE &&
1669 action->type != FLT_ACTION_COPY)
1670 continue;
1671
1672 if (action->str_value &&
1673 !strncmp(old_path, action->str_value, oldpathlen)) {
1674 base = action->str_value + oldpathlen;
1675 if (*base != '/' && *base != '\0')
1676 continue;
1677 while (*base == '/') base++;
1678 if (*base == '\0')
1679 dest_path = g_strdup(new_path);
1680 else
1681 dest_path = g_strconcat(new_path, "/", base,
1682 NULL);
1683 debug_print("filter_rule_rename_dest_path(): "
1684 "renaming %s -> %s\n",
1685 action->str_value, dest_path);
1686 g_free(action->str_value);
1687 action->str_value = dest_path;
1688 }
1689 }
1690 }
1691
filter_rule_delete_action_by_dest_path(FilterRule * rule,const gchar * path)1692 void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path)
1693 {
1694 FilterAction *action;
1695 GSList *cur;
1696 GSList *next;
1697 gint pathlen;
1698
1699 pathlen = strlen(path);
1700
1701 for (cur = rule->action_list; cur != NULL; cur = next) {
1702 action = (FilterAction *)cur->data;
1703 next = cur->next;
1704
1705 if (action->type != FLT_ACTION_MOVE &&
1706 action->type != FLT_ACTION_COPY)
1707 continue;
1708
1709 if (action->str_value &&
1710 !strncmp(path, action->str_value, pathlen) &&
1711 (action->str_value[pathlen] == '/' ||
1712 action->str_value[pathlen] == '\0')) {
1713 debug_print("filter_rule_delete_action_by_dest_path(): "
1714 "deleting %s\n", action->str_value);
1715 rule->action_list = g_slist_remove
1716 (rule->action_list, action);
1717 filter_action_free(action);
1718 }
1719 }
1720 }
1721
filter_list_rename_path(const gchar * old_path,const gchar * new_path)1722 void filter_list_rename_path(const gchar *old_path, const gchar *new_path)
1723 {
1724 GSList *cur;
1725
1726 g_return_if_fail(old_path != NULL);
1727 g_return_if_fail(new_path != NULL);
1728
1729 for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) {
1730 FilterRule *rule = (FilterRule *)cur->data;
1731 filter_rule_rename_dest_path(rule, old_path, new_path);
1732 }
1733
1734 filter_write_config();
1735 }
1736
filter_list_delete_path(const gchar * path)1737 void filter_list_delete_path(const gchar *path)
1738 {
1739 GSList *cur;
1740 GSList *next;
1741
1742 g_return_if_fail(path != NULL);
1743
1744 for (cur = prefs_common.fltlist; cur != NULL; cur = next) {
1745 FilterRule *rule = (FilterRule *)cur->data;
1746 next = cur->next;
1747
1748 filter_rule_delete_action_by_dest_path(rule, path);
1749 if (!rule->action_list) {
1750 prefs_common.fltlist =
1751 g_slist_remove(prefs_common.fltlist, rule);
1752 filter_rule_free(rule);
1753 }
1754 }
1755
1756 filter_write_config();
1757 }
1758
filter_rule_match_type_str_to_enum(const gchar * match_type,FilterMatchType * type,FilterMatchFlag * flag)1759 void filter_rule_match_type_str_to_enum(const gchar *match_type,
1760 FilterMatchType *type,
1761 FilterMatchFlag *flag)
1762 {
1763 g_return_if_fail(match_type != NULL);
1764
1765 *type = FLT_CONTAIN;
1766 *flag = 0;
1767
1768 if (!strcmp(match_type, "contains")) {
1769 *type = FLT_CONTAIN;
1770 } else if (!strcmp(match_type, "not-contain")) {
1771 *type = FLT_CONTAIN;
1772 *flag = FLT_NOT_MATCH;
1773 } else if (!strcmp(match_type, "is")) {
1774 *type = FLT_EQUAL;
1775 } else if (!strcmp(match_type, "is-not")) {
1776 *type = FLT_EQUAL;
1777 *flag = FLT_NOT_MATCH;
1778 } else if (!strcmp(match_type, "regex")) {
1779 *type = FLT_REGEX;
1780 } else if (!strcmp(match_type, "not-regex")) {
1781 *type = FLT_REGEX;
1782 *flag = FLT_NOT_MATCH;
1783 } else if (!strcmp(match_type, "in-addressbook")) {
1784 *type = FLT_IN_ADDRESSBOOK;
1785 } else if (!strcmp(match_type, "not-in-addressbook")) {
1786 *type = FLT_IN_ADDRESSBOOK;
1787 *flag = FLT_NOT_MATCH;
1788 } else if (!strcmp(match_type, "gt")) {
1789 } else if (!strcmp(match_type, "lt")) {
1790 *flag = FLT_NOT_MATCH;
1791 }
1792 }
1793
filter_get_keyword_from_msg(MsgInfo * msginfo,gchar ** header,gchar ** key,FilterCreateType type)1794 void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key,
1795 FilterCreateType type)
1796 {
1797 static HeaderEntry hentry[] = {{"List-Id:", NULL, TRUE},
1798 {"X-ML-Name:", NULL, TRUE},
1799 {"X-List:", NULL, TRUE},
1800 {"X-Mailing-list:", NULL, TRUE},
1801 {"X-Sequence:", NULL, TRUE},
1802 {NULL, NULL, FALSE}};
1803 enum
1804 {
1805 H_LIST_ID = 0,
1806 H_X_ML_NAME = 1,
1807 H_X_LIST = 2,
1808 H_X_MAILING_LIST = 3,
1809 H_X_SEQUENCE = 4
1810 };
1811
1812 FILE *fp;
1813
1814 g_return_if_fail(msginfo != NULL);
1815 g_return_if_fail(header != NULL);
1816 g_return_if_fail(key != NULL);
1817
1818 *header = NULL;
1819 *key = NULL;
1820
1821 switch (type) {
1822 case FLT_BY_NONE:
1823 return;
1824 case FLT_BY_AUTO:
1825 if ((fp = procmsg_open_message(msginfo)) == NULL)
1826 return;
1827 procheader_get_header_fields(fp, hentry);
1828 fclose(fp);
1829
1830 #define SET_FILTER_KEY(hstr, idx) \
1831 { \
1832 *header = g_strdup(hstr); \
1833 *key = hentry[idx].body; \
1834 hentry[idx].body = NULL; \
1835 }
1836
1837 if (hentry[H_LIST_ID].body != NULL) {
1838 SET_FILTER_KEY("List-Id", H_LIST_ID);
1839 extract_list_id_str(*key);
1840 } else if (hentry[H_X_ML_NAME].body != NULL) {
1841 SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME);
1842 } else if (hentry[H_X_LIST].body != NULL) {
1843 SET_FILTER_KEY("X-List", H_X_LIST);
1844 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
1845 SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST);
1846 } else if (hentry[H_X_SEQUENCE].body != NULL) {
1847 gchar *p;
1848
1849 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
1850 p = *key;
1851 while (*p != '\0') {
1852 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
1853 while (g_ascii_isspace(*p)) p++;
1854 if (g_ascii_isdigit(*p)) {
1855 *p = '\0';
1856 break;
1857 }
1858 }
1859 g_strstrip(*key);
1860 } else if (msginfo->subject) {
1861 *header = g_strdup("Subject");
1862 *key = g_strdup(msginfo->subject);
1863 }
1864
1865 #undef SET_FILTER_KEY
1866
1867 g_free(hentry[H_LIST_ID].body);
1868 hentry[H_LIST_ID].body = NULL;
1869 g_free(hentry[H_X_ML_NAME].body);
1870 hentry[H_X_ML_NAME].body = NULL;
1871 g_free(hentry[H_X_LIST].body);
1872 hentry[H_X_LIST].body = NULL;
1873 g_free(hentry[H_X_MAILING_LIST].body);
1874 hentry[H_X_MAILING_LIST].body = NULL;
1875
1876 break;
1877 case FLT_BY_FROM:
1878 *header = g_strdup("From");
1879 *key = g_strdup(msginfo->from);
1880 break;
1881 case FLT_BY_TO:
1882 *header = g_strdup("To");
1883 *key = g_strdup(msginfo->to);
1884 break;
1885 case FLT_BY_SUBJECT:
1886 *header = g_strdup("Subject");
1887 *key = g_strdup(msginfo->subject);
1888 break;
1889 default:
1890 break;
1891 }
1892 }
1893
filter_rule_list_free(GSList * fltlist)1894 void filter_rule_list_free(GSList *fltlist)
1895 {
1896 GSList *cur;
1897
1898 for (cur = fltlist; cur != NULL; cur = cur->next)
1899 filter_rule_free((FilterRule *)cur->data);
1900 g_slist_free(fltlist);
1901 }
1902
filter_rule_free(FilterRule * rule)1903 void filter_rule_free(FilterRule *rule)
1904 {
1905 if (!rule) return;
1906
1907 g_free(rule->name);
1908 g_free(rule->target_folder);
1909
1910 filter_cond_list_free(rule->cond_list);
1911 filter_action_list_free(rule->action_list);
1912
1913 g_free(rule);
1914 }
1915
filter_cond_list_free(GSList * cond_list)1916 void filter_cond_list_free(GSList *cond_list)
1917 {
1918 GSList *cur;
1919
1920 for (cur = cond_list; cur != NULL; cur = cur->next)
1921 filter_cond_free((FilterCond *)cur->data);
1922 g_slist_free(cond_list);
1923 }
1924
filter_action_list_free(GSList * action_list)1925 void filter_action_list_free(GSList *action_list)
1926 {
1927 GSList *cur;
1928
1929 for (cur = action_list; cur != NULL; cur = cur->next)
1930 filter_action_free((FilterAction *)cur->data);
1931 g_slist_free(action_list);
1932 }
1933
filter_cond_free(FilterCond * cond)1934 static void filter_cond_free(FilterCond *cond)
1935 {
1936 g_free(cond->header_name);
1937 g_free(cond->str_value);
1938 g_free(cond);
1939 }
1940
filter_action_free(FilterAction * action)1941 static void filter_action_free(FilterAction *action)
1942 {
1943 g_free(action->str_value);
1944 g_free(action);
1945 }
1946
filter_info_free(FilterInfo * fltinfo)1947 void filter_info_free(FilterInfo *fltinfo)
1948 {
1949 g_slist_free(fltinfo->dest_list);
1950 g_free(fltinfo);
1951 }
1952