Statistics
| Revision:

root / libsylph / filter.c @ 2474

History | View | Annotate | Download (40.6 kB)

1
/*
2
 * LibSylph -- E-Mail client library
3
 * Copyright (C) 1999-2009 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 <onigposix.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
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
        ret = filter_apply_msginfo(fltlist, msginfo, fltinfo);
89
90
        procmsg_msginfo_free(msginfo);
91
92
        return ret;
93
}
94
95
gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo,
96
                          FilterInfo *fltinfo)
97
{
98
        gchar *file;
99
        GSList *hlist, *cur;
100
        FilterRule *rule;
101
        gint ret = 0;
102
103
        g_return_val_if_fail(msginfo != NULL, -1);
104
        g_return_val_if_fail(fltinfo != NULL, -1);
105
106
        fltinfo->error = FLT_ERROR_OK;
107
108
        if (!fltlist) return 0;
109
110
        file = procmsg_get_message_file(msginfo);
111
        if (!file)
112
                return -1;
113
        hlist = procheader_get_header_list_from_file(file);
114
        if (!hlist) {
115
                g_free(file);
116
                return 0;
117
        }
118
119
        procmsg_set_auto_decrypt_message(FALSE);
120
121
        for (cur = fltlist; cur != NULL; cur = cur->next) {
122
                gboolean matched;
123
124
                rule = (FilterRule *)cur->data;
125
                if (!rule->enabled) continue;
126
                matched = filter_match_rule(rule, msginfo, hlist, fltinfo);
127
                if (fltinfo->error != FLT_ERROR_OK) {
128
                        g_warning("filter_match_rule() returned error (code: %d)\n", fltinfo->error);
129
                }
130
                if (matched) {
131
                        ret = filter_action_exec(rule, msginfo, file, fltinfo);
132
                        if (ret < 0) {
133
                                g_warning("filter_action_exec() returned error (code: %d)\n", fltinfo->error);
134
                                break;
135
                        }
136
                        if (fltinfo->drop_done == TRUE ||
137
                            fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE)
138
                                break;
139
                }
140
        }
141
142
        procmsg_set_auto_decrypt_message(TRUE);
143
144
        procheader_header_list_destroy(hlist);
145
        g_free(file);
146
147
        return ret;
148
}
149
150
gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file,
151
                        FilterInfo *fltinfo)
152
{
153
        FolderItem *dest_folder = NULL;
154
        FilterAction *action;
155
        GSList *cur;
156
        gchar *cmdline;
157
        gboolean copy_to_self = FALSE;
158
159
        g_return_val_if_fail(rule != NULL, -1);
160
        g_return_val_if_fail(msginfo != NULL, -1);
161
        g_return_val_if_fail(file != NULL, -1);
162
        g_return_val_if_fail(fltinfo != NULL, -1);
163
164
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
165
                action = (FilterAction *)cur->data;
166
167
                switch (action->type) {
168
                case FLT_ACTION_MARK:
169
                        debug_print("filter_action_exec(): mark\n");
170
                        MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED);
171
                        fltinfo->actions[action->type] = TRUE;
172
                        break;
173
                case FLT_ACTION_COLOR_LABEL:
174
                        debug_print("filter_action_exec(): color label: %d\n",
175
                                    action->int_value);
176
                        MSG_UNSET_PERM_FLAGS(fltinfo->flags,
177
                                             MSG_CLABEL_FLAG_MASK);
178
                        MSG_SET_COLORLABEL_VALUE(fltinfo->flags,
179
                                                 action->int_value);
180
                        fltinfo->actions[action->type] = TRUE;
181
                        break;
182
                case FLT_ACTION_MARK_READ:
183
                        debug_print("filter_action_exec(): mark as read\n");
184
                        if (msginfo->folder) {
185
                                if (MSG_IS_NEW(fltinfo->flags))
186
                                        msginfo->folder->new--;
187
                                if (MSG_IS_UNREAD(fltinfo->flags))
188
                                        msginfo->folder->unread--;
189
                        }
190
                        MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD);
191
                        fltinfo->actions[action->type] = TRUE;
192
                        break;
193
                case FLT_ACTION_EXEC:
194
                        cmdline = g_strconcat(action->str_value, " \"", file,
195
                                              "\"", NULL);
196
                        execute_command_line(cmdline, FALSE);
197
                        g_free(cmdline);
198
                        fltinfo->actions[action->type] = TRUE;
199
                        break;
200
                case FLT_ACTION_EXEC_ASYNC:
201
                        cmdline = g_strconcat(action->str_value, " \"", file,
202
                                              "\"", NULL);
203
                        execute_command_line(cmdline, TRUE);
204
                        g_free(cmdline);
205
                        fltinfo->actions[action->type] = TRUE;
206
                        break;
207
                default:
208
                        break;
209
                }
210
        }
211
212
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
213
                action = (FilterAction *)cur->data;
214
215
                switch (action->type) {
216
                case FLT_ACTION_MOVE:
217
                case FLT_ACTION_COPY:
218
                        dest_folder = folder_find_item_from_identifier
219
                                (action->str_value);
220
                        if (!dest_folder) {
221
                                g_warning("dest folder '%s' not found\n",
222
                                          action->str_value);
223
                                fltinfo->error = FLT_ERROR_ERROR;
224
                                return -1;
225
                        }
226
                        debug_print("filter_action_exec(): %s: dest_folder = %s\n",
227
                                    action->type == FLT_ACTION_COPY ?
228
                                    "copy" : "move", action->str_value);
229
230
                        if (msginfo->folder) {
231
                                gint val;
232
233
                                /* local filtering */
234
                                if (msginfo->folder == dest_folder)
235
                                        copy_to_self = TRUE;
236
                                else {
237
                                        if (action->type == FLT_ACTION_COPY) {
238
                                                MsgFlags save_flags;
239
240
                                                save_flags = msginfo->flags;
241
                                                msginfo->flags = fltinfo->flags;
242
                                                val = folder_item_copy_msg
243
                                                        (dest_folder, msginfo);
244
                                                msginfo->flags = save_flags;
245
                                                if (val == -1) {
246
                                                        fltinfo->error = FLT_ERROR_ERROR;
247
                                                        return -1;
248
                                                }
249
                                        }
250
                                        fltinfo->actions[action->type] = TRUE;
251
                                }
252
                        } else {
253
                                MsgFlags save_flags;
254
255
                                save_flags = msginfo->flags;
256
                                msginfo->flags = fltinfo->flags;
257
                                if (folder_item_add_msg_msginfo
258
                                        (dest_folder, msginfo, FALSE) < 0) {
259
                                        msginfo->flags = save_flags;
260
                                        fltinfo->error = FLT_ERROR_ERROR;
261
                                        return -1;
262
                                }
263
                                msginfo->flags = save_flags;
264
                                fltinfo->actions[action->type] = TRUE;
265
                        }
266
267
                        fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
268
                                                            dest_folder);
269
                        if (action->type == FLT_ACTION_MOVE) {
270
                                fltinfo->move_dest = dest_folder;
271
                                fltinfo->drop_done = TRUE;
272
                        }
273
                        break;
274
                default:
275
                        break;
276
                }
277
        }
278
279
        if (fltinfo->drop_done == TRUE)
280
                return 0;
281
282
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
283
                action = (FilterAction *)cur->data;
284
285
                switch (action->type) {
286
                case FLT_ACTION_NOT_RECEIVE:
287
                        debug_print("filter_action_exec(): don't receive\n");
288
                        fltinfo->drop_done = TRUE;
289
                        fltinfo->actions[action->type] = TRUE;
290
                        return 0;
291
                case FLT_ACTION_DELETE:
292
                        debug_print("filter_action_exec(): delete\n");
293
                        if (msginfo->folder) {
294
                                /* local filtering */
295
                                if (copy_to_self == FALSE)
296
                                        fltinfo->actions[action->type] = TRUE;
297
                        } else
298
                                fltinfo->actions[action->type] = TRUE;
299
300
                        fltinfo->drop_done = TRUE;
301
                        return 0;
302
                case FLT_ACTION_STOP_EVAL:
303
                        debug_print("filter_action_exec(): stop evaluation\n");
304
                        fltinfo->actions[action->type] = TRUE;
305
                        return 0;
306
                default:
307
                        break;
308
                }
309
        }
310
311
        return 0;
312
}
313
314
static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
315
{
316
#if defined(USE_ONIGURUMA) || defined(HAVE_REGCOMP)
317
        gint ret = 0;
318
        regex_t preg;
319
320
#if USE_ONIGURUMA
321
        reg_set_encoding(REG_POSIX_ENCODING_UTF8);
322
#endif
323
        ret = regcomp(&preg, needle, REG_ICASE|REG_EXTENDED);
324
        if (ret != 0)
325
                return FALSE;
326
327
        ret = regexec(&preg, haystack, 0, NULL, 0);
328
        regfree(&preg);
329
330
        if (ret == 0)
331
                return TRUE;
332
        else
333
#endif
334
                return FALSE;
335
}
336
337
gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist,
338
                           FilterInfo *fltinfo)
339
{
340
        FilterCond *cond;
341
        GSList *cur;
342
        gboolean matched;
343
344
        g_return_val_if_fail(rule->cond_list != NULL, FALSE);
345
346
        switch (rule->timing) {
347
        case FLT_TIMING_ANY:
348
                break;
349
        case FLT_TIMING_ON_RECEIVE:
350
                if (msginfo->folder != NULL)
351
                        return FALSE;
352
                break;
353
        case FLT_TIMING_MANUAL:
354
                if (msginfo->folder == NULL)
355
                        return FALSE;
356
                break;
357
        default:
358
                break;
359
        }
360
361
        if (rule->bool_op == FLT_AND) {
362
                for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
363
                        cond = (FilterCond *)cur->data;
364
                        matched = filter_match_cond(cond, msginfo, hlist,
365
                                                    fltinfo);
366
                        if (matched == FALSE)
367
                                return FALSE;
368
                }
369
370
                return TRUE;
371
        } else if (rule->bool_op == FLT_OR) {
372
                for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
373
                        cond = (FilterCond *)cur->data;
374
                        matched = filter_match_cond(cond, msginfo, hlist,
375
                                                    fltinfo);
376
                        if (matched == TRUE)
377
                                return TRUE;
378
                }
379
380
                return FALSE;
381
        }
382
383
        return FALSE;
384
}
385
386
static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo,
387
                                  GSList *hlist, FilterInfo *fltinfo)
388
{
389
        gint ret;
390
        gboolean matched = FALSE;
391
        gchar *file;
392
        gchar *cmdline;
393
        PrefsAccount *cond_ac;
394
395
        switch (cond->type) {
396
        case FLT_COND_HEADER:
397
                if (cond->match_type == FLT_IN_ADDRESSBOOK)
398
                        return filter_match_in_addressbook(cond, hlist, fltinfo);
399
                else
400
                        return filter_match_header_cond(cond, hlist);
401
        case FLT_COND_ANY_HEADER:
402
                return filter_match_header_cond(cond, hlist);
403
        case FLT_COND_TO_OR_CC:
404
                if (cond->match_type == FLT_IN_ADDRESSBOOK)
405
                        return filter_match_in_addressbook(cond, hlist, fltinfo);
406
                else
407
                        return filter_match_header_cond(cond, hlist);
408
        case FLT_COND_BODY:
409
                matched = procmime_find_string(msginfo, cond->str_value,
410
                                               cond->match_func);
411
                break;
412
        case FLT_COND_CMD_TEST:
413
                file = procmsg_get_message_file(msginfo);
414
                if (!file)
415
                        return FALSE;
416
                cmdline = g_strconcat(cond->str_value, " \"", file, "\"", NULL);
417
                ret = execute_command_line_async_wait(cmdline);
418
                matched = (ret == 0);
419
                if (ret == -1)
420
                        fltinfo->error = FLT_ERROR_EXEC_FAILED;
421
                g_free(cmdline);
422
                g_free(file);
423
                break;
424
        case FLT_COND_SIZE_GREATER:
425
                matched = (msginfo->size > cond->int_value * 1024);
426
                break;
427
        case FLT_COND_AGE_GREATER:
428
                matched = (time(NULL) - msginfo->date_t >
429
                                cond->int_value * 24 * 60 * 60);
430
                break;
431
        case FLT_COND_UNREAD:
432
                matched = MSG_IS_UNREAD(msginfo->flags);
433
                break;
434
        case FLT_COND_MARK:
435
                matched = MSG_IS_MARKED(msginfo->flags);
436
                break;
437
        case FLT_COND_COLOR_LABEL:
438
                matched = (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != 0);
439
                break;
440
        case FLT_COND_MIME:
441
                matched = MSG_IS_MIME(msginfo->flags);
442
                break;
443
        case FLT_COND_ACCOUNT:
444
                cond_ac = account_find_from_id(cond->int_value);
445
                matched = (cond_ac != NULL && cond_ac == fltinfo->account);
446
                break;
447
        default:
448
                g_warning("filter_match_cond(): unknown condition: %d\n",
449
                          cond->type);
450
                fltinfo->error = FLT_ERROR_ERROR;
451
                return FALSE;
452
        }
453
454
        if (FLT_IS_NOT_MATCH(cond->match_flag))
455
                matched = !matched;
456
457
        return matched;
458
}
459
460
static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist)
461
{
462
        gboolean matched = FALSE;
463
        GSList *cur;
464
        Header *header;
465
466
        for (cur = hlist; cur != NULL; cur = cur->next) {
467
                header = (Header *)cur->data;
468
469
                switch (cond->type) {
470
                case FLT_COND_HEADER:
471
                        if (!g_ascii_strcasecmp
472
                                (header->name, cond->header_name)) {
473
                                if (!cond->str_value ||
474
                                    cond->match_func(header->body,
475
                                                     cond->str_value))
476
                                        matched = TRUE;
477
                        }
478
                        break;
479
                case FLT_COND_ANY_HEADER:
480
                        if (!cond->str_value ||
481
                            cond->match_func(header->body, cond->str_value))
482
                                matched = TRUE;
483
                        break;
484
                case FLT_COND_TO_OR_CC:
485
                        if (!g_ascii_strcasecmp(header->name, "To") ||
486
                            !g_ascii_strcasecmp(header->name, "Cc")) {
487
                                if (!cond->str_value ||
488
                                    cond->match_func(header->body,
489
                                                     cond->str_value))
490
                                        matched = TRUE;
491
                        }
492
                        break;
493
                default:
494
                        break;
495
                }
496
497
                if (matched == TRUE)
498
                        break;
499
        }
500
501
        if (FLT_IS_NOT_MATCH(cond->match_flag))
502
                matched = !matched;
503
504
        return matched;
505
}
506
507
static gboolean filter_match_in_addressbook(FilterCond *cond, GSList *hlist,
508
                                            FilterInfo *fltinfo)
509
{
510
        gboolean matched = FALSE;
511
        GSList *cur;
512
        Header *header;
513
514
        if (!default_addrbook_func)
515
                return FALSE;
516
        if (cond->type != FLT_COND_HEADER && cond->type != FLT_COND_TO_OR_CC)
517
                return FALSE;
518
519
        for (cur = hlist; cur != NULL; cur = cur->next) {
520
                header = (Header *)cur->data;
521
522
                if (cond->type == FLT_COND_HEADER) {
523
                        if (!g_ascii_strcasecmp
524
                                (header->name, cond->header_name)) {
525
                                if (default_addrbook_func(header->body))
526
                                        matched = TRUE;
527
                        }
528
                } else if (cond->type == FLT_COND_TO_OR_CC) {
529
                        if (!g_ascii_strcasecmp(header->name, "To") ||
530
                            !g_ascii_strcasecmp(header->name, "Cc")) {
531
                                if (default_addrbook_func(header->body))
532
                                        matched = TRUE;
533
                        }
534
                }
535
536
                if (matched == TRUE)
537
                        break;
538
        }
539
540
        if (FLT_IS_NOT_MATCH(cond->match_flag))
541
                matched = !matched;
542
543
        return matched;
544
}
545
546
gboolean filter_rule_requires_full_headers(FilterRule *rule)
547
{
548
        GSList *cur;
549
550
        for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
551
                FilterCond *cond = (FilterCond *)cur->data;
552
                const gchar *name = cond->header_name;
553
554
                if (cond->type == FLT_COND_HEADER && name) {
555
                        if (g_ascii_strcasecmp(name, "Date") != 0 &&
556
                            g_ascii_strcasecmp(name, "From") != 0 &&
557
                            g_ascii_strcasecmp(name, "To") != 0 &&
558
                            g_ascii_strcasecmp(name, "Newsgroups") != 0 &&
559
                            g_ascii_strcasecmp(name, "Subject") != 0)
560
                                return TRUE;
561
                } else if (cond->type == FLT_COND_ANY_HEADER ||
562
                           cond->type == FLT_COND_TO_OR_CC)
563
                        return TRUE;
564
        }
565
566
        return FALSE;
567
}
568
569
#define RETURN_IF_TAG_NOT_MATCH(tag_name)                        \
570
        if (strcmp2(xmlnode->tag->tag, tag_name) != 0) {        \
571
                g_warning("tag name != \"" tag_name "\"\n");        \
572
                filter_cond_list_free(cond_list);                \
573
                filter_action_list_free(cond_list);                \
574
                return FALSE;                                        \
575
        }                                                        \
576
577
#define STR_SWITCH(sw)                { const gchar *sw_str = sw;
578
#define STR_CASE_BEGIN(str)        if (!strcmp(sw_str, str)) {
579
#define STR_CASE(str)                } else if (!strcmp(sw_str, str)) {
580
#define STR_CASE_END                } }
581
582
static gboolean filter_xml_node_func(GNode *node, gpointer data)
583
{
584
        XMLNode *xmlnode;
585
        GList *list;
586
        const gchar *name = NULL;
587
        FilterTiming timing = FLT_TIMING_ANY;
588
        gboolean enabled = TRUE;
589
        FilterRule *rule;
590
        FilterBoolOp bool_op = FLT_OR;
591
        const gchar *target_folder = NULL;
592
        gboolean recursive = FALSE;
593
        GSList *cond_list = NULL;
594
        GSList *action_list = NULL;
595
        GNode *child, *cond_child, *action_child;
596
        GSList **fltlist = (GSList **)data;
597
598
        if (g_node_depth(node) != 2) return FALSE;
599
        g_return_val_if_fail(node->data != NULL, FALSE);
600
601
        xmlnode = node->data;
602
        RETURN_IF_TAG_NOT_MATCH("rule");
603
604
        for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
605
                XMLAttr *attr = (XMLAttr *)list->data;
606
607
                if (!attr || !attr->name || !attr->value) continue;
608
                if (!strcmp(attr->name, "name"))
609
                        name = attr->value;
610
                else if (!strcmp(attr->name, "timing")) {
611
                        if (!strcmp(attr->value, "any"))
612
                                timing = FLT_TIMING_ANY;
613
                        else if (!strcmp(attr->value, "receive"))
614
                                timing = FLT_TIMING_ON_RECEIVE;
615
                        else if (!strcmp(attr->value, "manual"))
616
                                timing = FLT_TIMING_MANUAL;
617
                } else if (!strcmp(attr->name, "enabled")) {
618
                        if (!strcmp(attr->value, "true"))
619
                                enabled = TRUE;
620
                        else
621
                                enabled = FALSE;
622
                }
623
        }
624
625
        /* condition list */
626
        child = node->children;
627
        if (!child) {
628
                g_warning("<rule> must have children\n");
629
                return FALSE;
630
        }
631
        xmlnode = child->data;
632
        RETURN_IF_TAG_NOT_MATCH("condition-list");
633
        for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
634
                XMLAttr *attr = (XMLAttr *)list->data;
635
636
                if (attr && attr->name && attr->value &&
637
                    !strcmp(attr->name, "bool")) {
638
                        if (!strcmp(attr->value, "or"))
639
                                bool_op = FLT_OR;
640
                        else
641
                                bool_op = FLT_AND;
642
                }
643
        }
644
645
        for (cond_child = child->children; cond_child != NULL;
646
             cond_child = cond_child->next) {
647
                const gchar *type = NULL;
648
                const gchar *name = NULL;
649
                const gchar *value = NULL;
650
                gboolean case_sens = FALSE;
651
                FilterCond *cond;
652
                FilterCondType cond_type = FLT_COND_HEADER;
653
                FilterMatchType match_type = FLT_CONTAIN;
654
                FilterMatchFlag match_flag = 0;
655
656
                xmlnode = cond_child->data;
657
                if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
658
659
                for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
660
                        XMLAttr *attr = (XMLAttr *)list->data;
661
662
                        if (!attr || !attr->name || !attr->value) continue;
663
664
                        STR_SWITCH(attr->name)
665
                        STR_CASE_BEGIN("type")
666
                                type = attr->value;
667
                        STR_CASE("name")
668
                                name = attr->value;
669
                        STR_CASE("case")
670
                                case_sens = TRUE;
671
                        STR_CASE("recursive")
672
                                if (!strcmp(attr->value, "true"))
673
                                        recursive = TRUE;
674
                                else
675
                                        recursive = FALSE;
676
                        STR_CASE_END
677
                }
678
679
                if (type) {
680
                        filter_rule_match_type_str_to_enum
681
                                (type, &match_type, &match_flag);
682
                }
683
                if (case_sens)
684
                        match_flag |= FLT_CASE_SENS;
685
                value = xmlnode->element;
686
687
                STR_SWITCH(xmlnode->tag->tag)
688
                STR_CASE_BEGIN("match-header")
689
                        cond_type = FLT_COND_HEADER;
690
                STR_CASE("match-any-header")
691
                        cond_type = FLT_COND_ANY_HEADER;
692
                STR_CASE("match-to-or-cc")
693
                        cond_type = FLT_COND_TO_OR_CC;
694
                STR_CASE("match-body-text")
695
                        cond_type = FLT_COND_BODY;
696
                STR_CASE("command-test")
697
                        cond_type = FLT_COND_CMD_TEST;
698
                STR_CASE("size")
699
                        cond_type = FLT_COND_SIZE_GREATER;
700
                STR_CASE("age")
701
                        cond_type = FLT_COND_AGE_GREATER;
702
                STR_CASE("unread")
703
                        cond_type = FLT_COND_UNREAD;
704
                STR_CASE("mark")
705
                        cond_type = FLT_COND_MARK;
706
                STR_CASE("color-label")
707
                        cond_type = FLT_COND_COLOR_LABEL;
708
                STR_CASE("mime")
709
                        cond_type = FLT_COND_MIME;
710
                STR_CASE("account-id")
711
                        cond_type = FLT_COND_ACCOUNT;
712
                STR_CASE("target-folder")
713
                        target_folder = value;
714
                        continue;
715
                STR_CASE_END
716
717
                cond = filter_cond_new(cond_type, match_type, match_flag,
718
                                       name, value);
719
                cond_list = g_slist_append(cond_list, cond);
720
        }
721
722
        /* action list */
723
        child = child->next;
724
        if (!child) {
725
                g_warning("<rule> must have multiple children\n");
726
                filter_cond_list_free(cond_list);
727
                return FALSE;
728
        }
729
        xmlnode = child->data;
730
        RETURN_IF_TAG_NOT_MATCH("action-list");
731
732
        for (action_child = child->children; action_child != NULL;
733
             action_child = action_child->next) {
734
                FilterAction *action;
735
                FilterActionType action_type = FLT_ACTION_NONE;
736
737
                xmlnode = action_child->data;
738
                if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
739
740
                STR_SWITCH(xmlnode->tag->tag)
741
                STR_CASE_BEGIN("move")
742
                        action_type = FLT_ACTION_MOVE;
743
                STR_CASE("copy")
744
                        action_type = FLT_ACTION_COPY;
745
                STR_CASE("not-receive")
746
                        action_type = FLT_ACTION_NOT_RECEIVE;
747
                STR_CASE("delete")
748
                        action_type = FLT_ACTION_DELETE;
749
                STR_CASE("exec")
750
                        action_type = FLT_ACTION_EXEC;
751
                STR_CASE("exec-async")
752
                        action_type = FLT_ACTION_EXEC_ASYNC;
753
                STR_CASE("mark")
754
                        action_type = FLT_ACTION_MARK;
755
                STR_CASE("color-label")
756
                        action_type = FLT_ACTION_COLOR_LABEL;
757
                STR_CASE("mark-as-read")
758
                        action_type = FLT_ACTION_MARK_READ;
759
                STR_CASE("forward")
760
                        action_type = FLT_ACTION_FORWARD;
761
                STR_CASE("forward-as-attachment")
762
                        action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT;
763
                STR_CASE("redirect")
764
                        action_type = FLT_ACTION_REDIRECT;
765
                STR_CASE("stop-eval")
766
                        action_type = FLT_ACTION_STOP_EVAL;
767
                STR_CASE_END
768
769
                action = filter_action_new(action_type, xmlnode->element);
770
                action_list = g_slist_append(action_list, action);
771
        }
772
773
        if (name && cond_list) {
774
                rule = filter_rule_new(name, bool_op, cond_list, action_list);
775
                rule->timing = timing;
776
                rule->enabled = enabled;
777
                if (target_folder) {
778
                        rule->target_folder = g_strdup(target_folder);
779
                        rule->recursive = recursive;
780
                }
781
                *fltlist = g_slist_prepend(*fltlist, rule);
782
        }
783
784
        return FALSE;
785
}
786
787
#undef RETURN_IF_TAG_NOT_MATCH
788
#undef STR_SWITCH
789
#undef STR_CASE_BEGIN
790
#undef STR_CASE
791
#undef STR_CASE_END
792
793
GSList *filter_xml_node_to_filter_list(GNode *node)
794
{
795
        GSList *fltlist = NULL;
796
797
        g_return_val_if_fail(node != NULL, NULL);
798
799
        g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
800
                        filter_xml_node_func, &fltlist);
801
        fltlist = g_slist_reverse(fltlist);
802
803
        return fltlist;
804
}
805
806
GSList *filter_read_file(const gchar *file)
807
{
808
        GNode *node;
809
        GSList *list;
810
811
        g_return_val_if_fail(file != NULL, NULL);
812
813
        debug_print("Reading %s\n", file);
814
815
        if (!is_file_exist(file))
816
                return NULL;
817
818
        node = xml_parse_file(file);
819
        if (!node) {
820
                g_warning("Can't parse %s\n", file);
821
                return NULL;
822
        }
823
824
        list = filter_xml_node_to_filter_list(node);
825
826
        xml_free_tree(node);
827
828
        return list;
829
}
830
831
void filter_read_config(void)
832
{
833
        gchar *rcpath;
834
835
        debug_print("Reading filter configuration...\n");
836
837
        /* remove all previous filter list */
838
        while (prefs_common.fltlist != NULL) {
839
                FilterRule *rule = (FilterRule *)prefs_common.fltlist->data;
840
841
                filter_rule_free(rule);
842
                prefs_common.fltlist = g_slist_remove(prefs_common.fltlist,
843
                                                      rule);
844
        }
845
846
        rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
847
                             NULL);
848
        prefs_common.fltlist = filter_read_file(rcpath);
849
        g_free(rcpath);
850
}
851
852
#define NODE_NEW(tag, text) \
853
        node = xml_node_new(xml_tag_new(tag), text)
854
#define ADD_ATTR(name, value) \
855
        xml_tag_add_attr(node->tag, xml_attr_new(name, value))
856
857
void filter_write_file(GSList *list, const gchar *file)
858
{
859
        PrefFile *pfile;
860
        GSList *cur;
861
862
        g_return_if_fail(file != NULL);
863
864
        if ((pfile = prefs_file_open(file)) == NULL) {
865
                g_warning("failed to write filter configuration to file: %s\n",
866
                          file);
867
                return;
868
        }
869
870
        xml_file_put_xml_decl(pfile->fp);
871
        fputs("\n<filter>\n", pfile->fp);
872
873
        for (cur = list; cur != NULL; cur = cur->next) {
874
                FilterRule *rule = (FilterRule *)cur->data;
875
                GSList *cur_cond;
876
                GSList *cur_action;
877
                gchar match_type[64];
878
                gchar nstr[16];
879
880
                fputs("    <rule name=\"", pfile->fp);
881
                xml_file_put_escape_str(pfile->fp, rule->name);
882
                fprintf(pfile->fp, "\" timing=\"%s\"",
883
                        rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" :
884
                        rule->timing == FLT_TIMING_MANUAL ? "manual" : "any");
885
                fprintf(pfile->fp, " enabled=\"%s\">\n",
886
                        rule->enabled ? "true" : "false");
887
888
                fprintf(pfile->fp, "        <condition-list bool=\"%s\">\n",
889
                        rule->bool_op == FLT_OR ? "or" : "and");
890
891
                for (cur_cond = rule->cond_list; cur_cond != NULL;
892
                     cur_cond = cur_cond->next) {
893
                        FilterCond *cond = (FilterCond *)cur_cond->data;
894
                        XMLNode *node = NULL;
895
896
                        switch (cond->match_type) {
897
                        case FLT_CONTAIN:
898
                                strncpy2(match_type,
899
                                         FLT_IS_NOT_MATCH(cond->match_flag)
900
                                         ? "not-contain" : "contains",
901
                                         sizeof(match_type));
902
                                break;
903
                        case FLT_EQUAL:
904
                                strncpy2(match_type,
905
                                         FLT_IS_NOT_MATCH(cond->match_flag)
906
                                         ? "is-not" : "is", sizeof(match_type));
907
                                break;
908
                        case FLT_REGEX:
909
                                strncpy2(match_type,
910
                                         FLT_IS_NOT_MATCH(cond->match_flag)
911
                                         ? "not-regex" : "regex",
912
                                         sizeof(match_type));
913
                                break;
914
                        case FLT_IN_ADDRESSBOOK:
915
                                strncpy2(match_type,
916
                                         FLT_IS_NOT_MATCH(cond->match_flag)
917
                                         ? "not-in-addressbook" : "in-addressbook",
918
                                         sizeof(match_type));
919
                                break;
920
                        default:
921
                                match_type[0] = '\0';
922
                                break;
923
                        }
924
925
                        switch (cond->type) {
926
                        case FLT_COND_HEADER:
927
                                NODE_NEW("match-header", cond->str_value);
928
                                ADD_ATTR("type", match_type);
929
                                ADD_ATTR("name", cond->header_name);
930
                                if (FLT_IS_CASE_SENS(cond->match_flag))
931
                                        ADD_ATTR("case", "true");
932
                                break;
933
                        case FLT_COND_ANY_HEADER:
934
                                NODE_NEW("match-any-header", cond->str_value);
935
                                ADD_ATTR("type", match_type);
936
                                if (FLT_IS_CASE_SENS(cond->match_flag))
937
                                        ADD_ATTR("case", "true");
938
                                break;
939
                        case FLT_COND_TO_OR_CC:
940
                                NODE_NEW("match-to-or-cc", cond->str_value);
941
                                ADD_ATTR("type", match_type);
942
                                if (FLT_IS_CASE_SENS(cond->match_flag))
943
                                        ADD_ATTR("case", "true");
944
                                break;
945
                        case FLT_COND_BODY:
946
                                NODE_NEW("match-body-text", cond->str_value);
947
                                ADD_ATTR("type", match_type);
948
                                if (FLT_IS_CASE_SENS(cond->match_flag))
949
                                        ADD_ATTR("case", "true");
950
                                break;
951
                        case FLT_COND_CMD_TEST:
952
                                NODE_NEW("command-test", cond->str_value);
953
                                break;
954
                        case FLT_COND_SIZE_GREATER:
955
                                NODE_NEW("size", itos_buf(nstr, cond->int_value));
956
                                ADD_ATTR("type",
957
                                         FLT_IS_NOT_MATCH(cond->match_flag)
958
                                         ? "lt" : "gt");
959
                                break;
960
                        case FLT_COND_AGE_GREATER:
961
                                NODE_NEW("age", itos_buf(nstr, cond->int_value));
962
                                ADD_ATTR("type",
963
                                         FLT_IS_NOT_MATCH(cond->match_flag)
964
                                         ? "lt" : "gt");
965
                                break;
966
                        case FLT_COND_UNREAD:
967
                                NODE_NEW("unread", NULL);
968
                                ADD_ATTR("type",
969
                                         FLT_IS_NOT_MATCH(cond->match_flag)
970
                                         ? "is-not" : "is");
971
                                break;
972
                        case FLT_COND_MARK:
973
                                NODE_NEW("mark", NULL);
974
                                ADD_ATTR("type",
975
                                         FLT_IS_NOT_MATCH(cond->match_flag)
976
                                         ? "is-not" : "is");
977
                                break;
978
                        case FLT_COND_COLOR_LABEL:
979
                                NODE_NEW("color-label", NULL);
980
                                ADD_ATTR("type",
981
                                         FLT_IS_NOT_MATCH(cond->match_flag)
982
                                         ? "is-not" : "is");
983
                                break;
984
                        case FLT_COND_MIME:
985
                                NODE_NEW("mime", NULL);
986
                                ADD_ATTR("type",
987
                                         FLT_IS_NOT_MATCH(cond->match_flag)
988
                                         ? "is-not" : "is");
989
                                break;
990
                        case FLT_COND_ACCOUNT:
991
                                 NODE_NEW("account-id", itos_buf(nstr, cond->int_value));
992
                                 break;
993
                        default:
994
                                 break;
995
                        }
996
997
                        if (node) {
998
                                fputs("            ", pfile->fp);
999
                                xml_file_put_node(pfile->fp, node);
1000
                                xml_free_node(node);
1001
                        }
1002
                }
1003
1004
                if (rule->target_folder) {
1005
                        XMLNode *node;
1006
1007
                        NODE_NEW("target-folder", rule->target_folder);
1008
                        ADD_ATTR("recursive", rule->recursive
1009
                                 ? "true" : "false");
1010
                        fputs("            ", pfile->fp);
1011
                        xml_file_put_node(pfile->fp, node);
1012
                        xml_free_node(node);
1013
                }
1014
1015
                fputs("        </condition-list>\n", pfile->fp);
1016
1017
                fputs("        <action-list>\n", pfile->fp);
1018
1019
                for (cur_action = rule->action_list; cur_action != NULL;
1020
                     cur_action = cur_action->next) {
1021
                        FilterAction *action = (FilterAction *)cur_action->data;
1022
                        XMLNode *node = NULL;
1023
1024
                        switch (action->type) {
1025
                        case FLT_ACTION_MOVE:
1026
                                NODE_NEW("move", action->str_value);
1027
                                break;
1028
                        case FLT_ACTION_COPY:
1029
                                NODE_NEW("copy", action->str_value);
1030
                                break;
1031
                        case FLT_ACTION_NOT_RECEIVE:
1032
                                NODE_NEW("not-receive", NULL);
1033
                                break;
1034
                        case FLT_ACTION_DELETE:
1035
                                NODE_NEW("delete", NULL);
1036
                                break;
1037
                        case FLT_ACTION_EXEC:
1038
                                NODE_NEW("exec", action->str_value);
1039
                                break;
1040
                        case FLT_ACTION_EXEC_ASYNC:
1041
                                NODE_NEW("exec-async", action->str_value);
1042
                                break;
1043
                        case FLT_ACTION_MARK:
1044
                                NODE_NEW("mark", NULL);
1045
                                break;
1046
                        case FLT_ACTION_COLOR_LABEL:
1047
                                NODE_NEW("color-label", action->str_value);
1048
                                break;
1049
                        case FLT_ACTION_MARK_READ:
1050
                                NODE_NEW("mark-as-read", NULL);
1051
                                break;
1052
                        case FLT_ACTION_FORWARD:
1053
                                NODE_NEW("forward", action->str_value);
1054
                                break;
1055
                        case FLT_ACTION_FORWARD_AS_ATTACHMENT:
1056
                                NODE_NEW("forward-as-attachment",
1057
                                         action->str_value);
1058
                                break;
1059
                        case FLT_ACTION_REDIRECT:
1060
                                NODE_NEW("redirect", action->str_value);
1061
                                break;
1062
                        case FLT_ACTION_STOP_EVAL:
1063
                                NODE_NEW("stop-eval", NULL);
1064
                                break;
1065
                        default:
1066
                                break;
1067
                        }
1068
1069
                        if (node) {
1070
                                fputs("            ", pfile->fp);
1071
                                xml_file_put_node(pfile->fp, node);
1072
                                xml_free_node(node);
1073
                        }
1074
                }
1075
1076
                fputs("        </action-list>\n", pfile->fp);
1077
1078
                fputs("    </rule>\n", pfile->fp);
1079
        }
1080
1081
        fputs("</filter>\n", pfile->fp);
1082
1083
        if (prefs_file_close(pfile) < 0) {
1084
                g_warning("failed to write filter configuration to file: %s\n",
1085
                          file);
1086
                return;
1087
        }
1088
}
1089
1090
void filter_write_config(void)
1091
{
1092
        gchar *rcpath;
1093
1094
        debug_print("Writing filter configuration...\n");
1095
1096
        rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1097
                             NULL);
1098
        filter_write_file(prefs_common.fltlist, rcpath);
1099
        g_free(rcpath);
1100
}
1101
1102
#undef NODE_NEW
1103
#undef ADD_ATTR
1104
1105
gchar *filter_get_str(FilterRule *rule)
1106
{
1107
        gchar *str;
1108
        FilterCond *cond1, *cond2;
1109
        FilterAction *action = NULL;
1110
        GSList *cur;
1111
        gint flag1 = 0, flag2 = 0;
1112
1113
        cond1 = (FilterCond *)rule->cond_list->data;
1114
        if (rule->cond_list->next)
1115
                cond2 = (FilterCond *)rule->cond_list->next->data;
1116
        else
1117
                cond2 = NULL;
1118
1119
        /* new -> old flag conversion */
1120
        switch (cond1->match_type) {
1121
        case FLT_CONTAIN:
1122
        case FLT_EQUAL:
1123
                flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN;
1124
                if (FLT_IS_CASE_SENS(cond1->match_flag))
1125
                        flag1 |= FLT_O_CASE_SENS;
1126
                break;
1127
        case FLT_REGEX:
1128
                flag1 = FLT_O_REGEX; break;
1129
        default:
1130
                break;
1131
        }
1132
        if (cond2) {
1133
                switch (cond2->match_type) {
1134
                case FLT_CONTAIN:
1135
                case FLT_EQUAL:
1136
                        flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN;
1137
                        if (FLT_IS_CASE_SENS(cond2->match_flag))
1138
                                flag2 |= FLT_O_CASE_SENS;
1139
                        break;
1140
                case FLT_REGEX:
1141
                        flag2 = FLT_O_REGEX; break;
1142
                default:
1143
                        break;
1144
                }
1145
        } else
1146
                flag2 = FLT_O_CONTAIN;
1147
1148
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1149
                action = (FilterAction *)cur->data;
1150
                if (action->type == FLT_ACTION_MOVE ||
1151
                    action->type == FLT_ACTION_NOT_RECEIVE ||
1152
                    action->type == FLT_ACTION_DELETE)
1153
                        break;
1154
        }
1155
1156
        str = g_strdup_printf
1157
                ("%s:%s:%c:%s:%s:%s:%d:%d:%c",
1158
                 cond1->header_name, cond1->str_value ? cond1->str_value : "",
1159
                 (cond2 && cond2->header_name) ?
1160
                         (rule->bool_op == FLT_AND ? '&' : '|') : ' ',
1161
                 (cond2 && cond2->header_name) ? cond2->header_name : "",
1162
                 (cond2 && cond2->str_value) ? cond2->str_value : "",
1163
                 (action && action->str_value) ? action->str_value : "",
1164
                 flag1, flag2,
1165
                 (action && action->type == FLT_ACTION_MOVE) ? 'm' :
1166
                 (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' :
1167
                 (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' ');
1168
1169
        return str;
1170
}
1171
1172
#define PARSE_ONE_PARAM(p, srcp) \
1173
{ \
1174
        p = strchr(srcp, '\t'); \
1175
        if (!p) return NULL; \
1176
        else \
1177
                *p++ = '\0'; \
1178
}
1179
1180
FilterRule *filter_read_str(const gchar *str)
1181
{
1182
        FilterRule *rule;
1183
        FilterBoolOp bool_op;
1184
        gint flag;
1185
        FilterCond *cond;
1186
        FilterMatchType match_type;
1187
        FilterMatchFlag match_flag;
1188
        FilterAction *action;
1189
        GSList *cond_list = NULL;
1190
        GSList *action_list = NULL;
1191
        gchar *tmp;
1192
        gchar *rule_name;
1193
        gchar *name1, *body1, *op, *name2, *body2, *dest;
1194
        gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL;
1195
1196
        Xstrdup_a(tmp, str, return NULL);
1197
1198
        name1 = tmp;
1199
        PARSE_ONE_PARAM(body1, name1);
1200
        PARSE_ONE_PARAM(op, body1);
1201
        PARSE_ONE_PARAM(name2, op);
1202
        PARSE_ONE_PARAM(body2, name2);
1203
        PARSE_ONE_PARAM(dest, body2);
1204
        if (strchr(dest, '\t')) {
1205
                gchar *p;
1206
1207
                PARSE_ONE_PARAM(flag1, dest);
1208
                PARSE_ONE_PARAM(flag2, flag1);
1209
                PARSE_ONE_PARAM(action1, flag2);
1210
                if ((p = strchr(action1, '\t'))) *p = '\0';
1211
        }
1212
1213
        bool_op = (*op == '&') ? FLT_AND : FLT_OR;
1214
1215
        if (*name1) {
1216
                if (flag1)
1217
                        flag = (FilterOldFlag)strtoul(flag1, NULL, 10);
1218
                else
1219
                        flag = FLT_O_CONTAIN;
1220
                match_type = FLT_CONTAIN;
1221
                match_flag = 0;
1222
                if (flag & FLT_O_REGEX)
1223
                        match_type = FLT_REGEX;
1224
                else if (!(flag & FLT_O_CONTAIN))
1225
                        match_flag = FLT_NOT_MATCH;
1226
                if (flag & FLT_O_CASE_SENS)
1227
                        match_flag |= FLT_CASE_SENS;
1228
                cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1229
                                       name1, body1);
1230
                cond_list = g_slist_append(cond_list, cond);
1231
        }
1232
        if (*name2) {
1233
                if (flag2)
1234
                        flag = (FilterOldFlag)strtoul(flag2, NULL, 10);
1235
                else
1236
                        flag = FLT_O_CONTAIN;
1237
                match_type = FLT_CONTAIN;
1238
                match_flag = 0;
1239
                if (flag & FLT_O_REGEX)
1240
                        match_type = FLT_REGEX;
1241
                else if (!(flag & FLT_O_CONTAIN))
1242
                        match_flag = FLT_NOT_MATCH;
1243
                if (flag & FLT_O_CASE_SENS)
1244
                        match_flag |= FLT_CASE_SENS;
1245
                cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1246
                                       name2, body2);
1247
                cond_list = g_slist_append(cond_list, cond);
1248
        }
1249
1250
        action = filter_action_new(FLT_ACTION_MOVE,
1251
                                   *dest ? g_strdup(dest) : NULL);
1252
        if (action1) {
1253
                switch (*action1) {
1254
                case 'm': action->type = FLT_ACTION_MOVE;                break;
1255
                case 'n': action->type = FLT_ACTION_NOT_RECEIVE;        break;
1256
                case 'd': action->type = FLT_ACTION_DELETE;                break;
1257
                default:  g_warning("Invalid action: `%c'\n", *action1);
1258
                }
1259
        }
1260
        action_list = g_slist_append(action_list, action);
1261
1262
        Xstrdup_a(rule_name, str, return NULL);
1263
        subst_char(rule_name, '\t', ':');
1264
1265
        rule = filter_rule_new(rule_name, bool_op, cond_list, action_list);
1266
1267
        return rule;
1268
}
1269
1270
void filter_set_addressbook_func(FilterInAddressBookFunc func)
1271
{
1272
        default_addrbook_func = func;
1273
}
1274
1275
FilterInAddressBookFunc filter_get_addressbook_func(void)
1276
{
1277
        return default_addrbook_func;
1278
}
1279
1280
FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op,
1281
                            GSList *cond_list, GSList *action_list)
1282
{
1283
        FilterRule *rule;
1284
1285
        rule = g_new0(FilterRule, 1);
1286
        rule->name = g_strdup(name);
1287
        rule->bool_op = bool_op;
1288
        rule->cond_list = cond_list;
1289
        rule->action_list = action_list;
1290
        rule->timing = FLT_TIMING_ANY;
1291
        rule->enabled = TRUE;
1292
1293
        return rule;
1294
}
1295
1296
FilterCond *filter_cond_new(FilterCondType type,
1297
                            FilterMatchType match_type,
1298
                            FilterMatchFlag match_flag,
1299
                            const gchar *header, const gchar *value)
1300
{
1301
        FilterCond *cond;
1302
1303
        cond = g_new0(FilterCond, 1);
1304
        cond->type = type;
1305
        cond->match_type = match_type;
1306
        cond->match_flag = match_flag;
1307
1308
        if (type == FLT_COND_HEADER)
1309
                cond->header_name =
1310
                        (header && *header) ? g_strdup(header) : NULL;
1311
        else
1312
                cond->header_name = NULL;
1313
1314
        cond->str_value = (value && *value) ? g_strdup(value) : NULL;
1315
        if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER)
1316
                cond->int_value = atoi(value);
1317
        else
1318
                cond->int_value = 0;
1319
1320
        if (match_type == FLT_REGEX)
1321
                cond->match_func = strmatch_regex;
1322
        else if (match_type == FLT_EQUAL) {
1323
                if (FLT_IS_CASE_SENS(match_flag))
1324
                        cond->match_func = str_find_equal;
1325
                else
1326
                        cond->match_func = str_case_find_equal;
1327
        } else if (match_type == FLT_IN_ADDRESSBOOK) {
1328
                cond->match_func = str_case_find_equal;
1329
        } else {
1330
                if (FLT_IS_CASE_SENS(match_flag))
1331
                        cond->match_func = str_find;
1332
                else
1333
                        cond->match_func = str_case_find;
1334
        }
1335
1336
        return cond;
1337
}
1338
1339
FilterAction *filter_action_new(FilterActionType type, const gchar *str)
1340
{
1341
        FilterAction *action;
1342
1343
        action = g_new0(FilterAction, 1);
1344
        action->type = type;
1345
1346
        action->str_value = (str && *str) ? g_strdup(str) : NULL;
1347
        if (type == FLT_ACTION_COLOR_LABEL && str)
1348
                action->int_value = atoi(str);
1349
        else
1350
                action->int_value = 0;
1351
1352
        return action;
1353
}
1354
1355
FilterInfo *filter_info_new(void)
1356
{
1357
        FilterInfo *fltinfo;
1358
1359
        fltinfo = g_new0(FilterInfo, 1);
1360
        fltinfo->dest_list = NULL;
1361
        fltinfo->move_dest = NULL;
1362
        fltinfo->drop_done = FALSE;
1363
        fltinfo->error = FLT_ERROR_OK;
1364
1365
        return fltinfo;
1366
}
1367
1368
void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path,
1369
                                  const gchar *new_path)
1370
{
1371
        FilterAction *action;
1372
        GSList *cur;
1373
        gchar *base;
1374
        gchar *dest_path;
1375
        gint oldpathlen;
1376
1377
        oldpathlen = strlen(old_path);
1378
1379
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1380
                action = (FilterAction *)cur->data;
1381
1382
                if (action->type != FLT_ACTION_MOVE &&
1383
                    action->type != FLT_ACTION_COPY)
1384
                        continue;
1385
1386
                if (action->str_value &&
1387
                    !strncmp(old_path, action->str_value, oldpathlen)) {
1388
                        base = action->str_value + oldpathlen;
1389
                        if (*base != '/' && *base != '\0')
1390
                                continue;
1391
                        while (*base == '/') base++;
1392
                        if (*base == '\0')
1393
                                dest_path = g_strdup(new_path);
1394
                        else
1395
                                dest_path = g_strconcat(new_path, "/", base,
1396
                                                        NULL);
1397
                        debug_print("filter_rule_rename_dest_path(): "
1398
                                    "renaming %s -> %s\n",
1399
                                    action->str_value, dest_path);
1400
                        g_free(action->str_value);
1401
                        action->str_value = dest_path;
1402
                }
1403
        }
1404
}
1405
1406
void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path)
1407
{
1408
        FilterAction *action;
1409
        GSList *cur;
1410
        GSList *next;
1411
        gint pathlen;
1412
1413
        pathlen = strlen(path);
1414
1415
        for (cur = rule->action_list; cur != NULL; cur = next) {
1416
                action = (FilterAction *)cur->data;
1417
                next = cur->next;
1418
1419
                if (action->type != FLT_ACTION_MOVE &&
1420
                    action->type != FLT_ACTION_COPY)
1421
                        continue;
1422
1423
                if (action->str_value &&
1424
                    !strncmp(path, action->str_value, pathlen) &&
1425
                    (action->str_value[pathlen] == '/' ||
1426
                     action->str_value[pathlen] == '\0')) {
1427
                        debug_print("filter_rule_delete_action_by_dest_path(): "
1428
                                    "deleting %s\n", action->str_value);
1429
                        rule->action_list = g_slist_remove
1430
                                (rule->action_list, action);
1431
                        filter_action_free(action);
1432
                }
1433
        }
1434
}
1435
1436
void filter_list_rename_path(const gchar *old_path, const gchar *new_path)
1437
{
1438
        GSList *cur;
1439
1440
        g_return_if_fail(old_path != NULL);
1441
        g_return_if_fail(new_path != NULL);
1442
1443
        for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) {
1444
                FilterRule *rule = (FilterRule *)cur->data;
1445
                filter_rule_rename_dest_path(rule, old_path, new_path);
1446
        }
1447
1448
        filter_write_config();
1449
}
1450
1451
void filter_list_delete_path(const gchar *path)
1452
{
1453
        GSList *cur;
1454
        GSList *next;
1455
1456
        g_return_if_fail(path != NULL);
1457
1458
        for (cur = prefs_common.fltlist; cur != NULL; cur = next) {
1459
                FilterRule *rule = (FilterRule *)cur->data;
1460
                next = cur->next;
1461
1462
                filter_rule_delete_action_by_dest_path(rule, path);
1463
                if (!rule->action_list) {
1464
                        prefs_common.fltlist =
1465
                                g_slist_remove(prefs_common.fltlist, rule);
1466
                        filter_rule_free(rule);
1467
                }
1468
        }
1469
1470
        filter_write_config();
1471
}
1472
1473
void filter_rule_match_type_str_to_enum(const gchar *match_type,
1474
                                        FilterMatchType *type,
1475
                                        FilterMatchFlag *flag)
1476
{
1477
        g_return_if_fail(match_type != NULL);
1478
1479
        *type = FLT_CONTAIN;
1480
        *flag = 0;
1481
1482
        if (!strcmp(match_type, "contains")) {
1483
                *type = FLT_CONTAIN;
1484
        } else if (!strcmp(match_type, "not-contain")) {
1485
                *type = FLT_CONTAIN;
1486
                *flag = FLT_NOT_MATCH;
1487
        } else if (!strcmp(match_type, "is")) {
1488
                *type = FLT_EQUAL;
1489
        } else if (!strcmp(match_type, "is-not")) {
1490
                *type = FLT_EQUAL;
1491
                *flag = FLT_NOT_MATCH;
1492
        } else if (!strcmp(match_type, "regex")) {
1493
                *type = FLT_REGEX;
1494
        } else if (!strcmp(match_type, "not-regex")) {
1495
                *type = FLT_REGEX;
1496
                *flag = FLT_NOT_MATCH;
1497
        } else if (!strcmp(match_type, "in-addressbook")) {
1498
                *type = FLT_IN_ADDRESSBOOK;
1499
        } else if (!strcmp(match_type, "not-in-addressbook")) {
1500
                *type = FLT_IN_ADDRESSBOOK;
1501
                *flag = FLT_NOT_MATCH;
1502
        } else if (!strcmp(match_type, "gt")) {
1503
        } else if (!strcmp(match_type, "lt")) {
1504
                *flag = FLT_NOT_MATCH;
1505
        }
1506
}
1507
1508
void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key,
1509
                                 FilterCreateType type)
1510
{
1511
        static HeaderEntry hentry[] = {{"List-Id:",           NULL, TRUE},
1512
                                       {"X-ML-Name:",           NULL, TRUE},
1513
                                       {"X-List:",           NULL, TRUE},
1514
                                       {"X-Mailing-list:", NULL, TRUE},
1515
                                       {"X-Sequence:",           NULL, TRUE},
1516
                                       {NULL,                   NULL, FALSE}};
1517
        enum
1518
        {
1519
                H_LIST_ID         = 0,
1520
                H_X_ML_NAME         = 1,
1521
                H_X_LIST         = 2,
1522
                H_X_MAILING_LIST = 3,
1523
                H_X_SEQUENCE         = 4
1524
        };
1525
1526
        FILE *fp;
1527
1528
        g_return_if_fail(msginfo != NULL);
1529
        g_return_if_fail(header != NULL);
1530
        g_return_if_fail(key != NULL);
1531
1532
        *header = NULL;
1533
        *key = NULL;
1534
1535
        switch (type) {
1536
        case FLT_BY_NONE:
1537
                return;
1538
        case FLT_BY_AUTO:
1539
                if ((fp = procmsg_open_message(msginfo)) == NULL)
1540
                        return;
1541
                procheader_get_header_fields(fp, hentry);
1542
                fclose(fp);
1543
1544
#define SET_FILTER_KEY(hstr, idx)        \
1545
{                                        \
1546
        *header = g_strdup(hstr);        \
1547
        *key = hentry[idx].body;        \
1548
        hentry[idx].body = NULL;        \
1549
}
1550
1551
                if (hentry[H_LIST_ID].body != NULL) {
1552
                        SET_FILTER_KEY("List-Id", H_LIST_ID);
1553
                        extract_list_id_str(*key);
1554
                } else if (hentry[H_X_ML_NAME].body != NULL) {
1555
                        SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME);
1556
                } else if (hentry[H_X_LIST].body != NULL) {
1557
                        SET_FILTER_KEY("X-List", H_X_LIST);
1558
                } else if (hentry[H_X_MAILING_LIST].body != NULL) {
1559
                        SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST);
1560
                } else if (hentry[H_X_SEQUENCE].body != NULL) {
1561
                        gchar *p;
1562
1563
                        SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
1564
                        p = *key;
1565
                        while (*p != '\0') {
1566
                                while (*p != '\0' && !g_ascii_isspace(*p)) p++;
1567
                                while (g_ascii_isspace(*p)) p++;
1568
                                if (g_ascii_isdigit(*p)) {
1569
                                        *p = '\0';
1570
                                        break;
1571
                                }
1572
                        }
1573
                        g_strstrip(*key);
1574
                } else if (msginfo->subject) {
1575
                        *header = g_strdup("Subject");
1576
                        *key = g_strdup(msginfo->subject);
1577
                }
1578
1579
#undef SET_FILTER_KEY
1580
1581
                g_free(hentry[H_LIST_ID].body);
1582
                hentry[H_LIST_ID].body = NULL;
1583
                g_free(hentry[H_X_ML_NAME].body);
1584
                hentry[H_X_ML_NAME].body = NULL;
1585
                g_free(hentry[H_X_LIST].body);
1586
                hentry[H_X_LIST].body = NULL;
1587
                g_free(hentry[H_X_MAILING_LIST].body);
1588
                hentry[H_X_MAILING_LIST].body = NULL;
1589
1590
                break;
1591
        case FLT_BY_FROM:
1592
                *header = g_strdup("From");
1593
                *key = g_strdup(msginfo->from);
1594
                break;
1595
        case FLT_BY_TO:
1596
                *header = g_strdup("To");
1597
                *key = g_strdup(msginfo->to);
1598
                break;
1599
        case FLT_BY_SUBJECT:
1600
                *header = g_strdup("Subject");
1601
                *key = g_strdup(msginfo->subject);
1602
                break;
1603
        default:
1604
                break;
1605
        }
1606
}
1607
1608
void filter_rule_list_free(GSList *fltlist)
1609
{
1610
        GSList *cur;
1611
1612
        for (cur = fltlist; cur != NULL; cur = cur->next)
1613
                filter_rule_free((FilterRule *)cur->data);
1614
        g_slist_free(fltlist);
1615
}
1616
1617
void filter_rule_free(FilterRule *rule)
1618
{
1619
        if (!rule) return;
1620
1621
        g_free(rule->name);
1622
        g_free(rule->target_folder);
1623
1624
        filter_cond_list_free(rule->cond_list);
1625
        filter_action_list_free(rule->action_list);
1626
1627
        g_free(rule);
1628
}
1629
1630
void filter_cond_list_free(GSList *cond_list)
1631
{
1632
        GSList *cur;
1633
1634
        for (cur = cond_list; cur != NULL; cur = cur->next)
1635
                filter_cond_free((FilterCond *)cur->data);
1636
        g_slist_free(cond_list);
1637
}
1638
1639
void filter_action_list_free(GSList *action_list)
1640
{
1641
        GSList *cur;
1642
1643
        for (cur = action_list; cur != NULL; cur = cur->next)
1644
                filter_action_free((FilterAction *)cur->data);
1645
        g_slist_free(action_list);
1646
}
1647
1648
static void filter_cond_free(FilterCond *cond)
1649
{
1650
        g_free(cond->header_name);
1651
        g_free(cond->str_value);
1652
        g_free(cond);
1653
}
1654
1655
static void filter_action_free(FilterAction *action)
1656
{
1657
        g_free(action->str_value);
1658
        g_free(action);
1659
}
1660
1661
void filter_info_free(FilterInfo *fltinfo)
1662
{
1663
        g_slist_free(fltinfo->dest_list);
1664
        g_free(fltinfo);
1665
}