Statistics
| Revision:

root / src / filter.c @ 1

History | View | Annotate | Download (29.8 KB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
 */
19

    
20
#ifdef HAVE_CONFIG_H
21
#  include "config.h"
22
#endif
23

    
24
#include "defs.h"
25

    
26
#include <glib.h>
27
#include <string.h>
28
#include <strings.h>
29
#include <stdlib.h>
30
#include <sys/types.h>
31
#include <regex.h>
32
#include <time.h>
33

    
34
#include "intl.h"
35
#include "procheader.h"
36
#include "filter.h"
37
#include "folder.h"
38
#include "utils.h"
39
#include "xml.h"
40
#include "prefs.h"
41
#include "prefs_account.h"
42

    
43
typedef enum
44
{
45
        FLT_O_CONTAIN        = 1 << 0,
46
        FLT_O_CASE_SENS        = 1 << 1,
47
        FLT_O_REGEX        = 1 << 2
48
} FilterOldFlag;
49

    
50
static gboolean filter_match_cond        (FilterCond        *cond,
51
                                         MsgInfo        *msginfo,
52
                                         GSList                *hlist,
53
                                         FilterInfo        *fltinfo);
54
static gboolean filter_match_header_cond(FilterCond        *cond,
55
                                         GSList                *hlist);
56

    
57
static void filter_cond_free                (FilterCond        *cond);
58
static void filter_action_free                (FilterAction        *action);
59

    
60

    
61
gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo)
62
{
63
        MsgInfo *msginfo;
64
        gint ret = 0;
65

    
66
        g_return_val_if_fail(file != NULL, -1);
67
        g_return_val_if_fail(fltinfo != NULL, -1);
68

    
69
        if (!fltlist) return 0;
70

    
71
        msginfo = procheader_parse_file(file, fltinfo->flags, FALSE);
72
        if (!msginfo) return 0;
73
        msginfo->file_path = g_strdup(file);
74

    
75
        ret = filter_apply_msginfo(fltlist, msginfo, fltinfo);
76

    
77
        procmsg_msginfo_free(msginfo);
78

    
79
        return ret;
80
}
81

    
82
gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo,
83
                          FilterInfo *fltinfo)
84
{
85
        gchar *file;
86
        GSList *hlist, *cur;
87
        FilterRule *rule;
88
        gint ret = 0;
89

    
90
        g_return_val_if_fail(msginfo != NULL, -1);
91
        g_return_val_if_fail(fltinfo != NULL, -1);
92

    
93
        if (!fltlist) return 0;
94

    
95
        file = procmsg_get_message_file(msginfo);
96
        hlist = procheader_get_header_list_from_file(file);
97
        if (!hlist) {
98
                g_free(file);
99
                return 0;
100
        }
101

    
102
        for (cur = fltlist; cur != NULL; cur = cur->next) {
103
                rule = (FilterRule *)cur->data;
104
                if (!rule->enabled) continue;
105
                if (filter_match_rule(rule, msginfo, hlist, fltinfo)) {
106
                        ret = filter_action_exec(rule, msginfo, file, fltinfo);
107
                        if (ret < 0) {
108
                                g_warning("filter_action_exec() returned error\n");
109
                                break;
110
                        }
111
                        if (fltinfo->drop_done == TRUE ||
112
                            fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE)
113
                                break;
114
                }
115
        }
116

    
117
        procheader_header_list_destroy(hlist);
118
        g_free(file);
119

    
120
        return ret;
121
}
122

    
123
gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file,
124
                        FilterInfo *fltinfo)
125
{
126
        FolderItem *dest_folder = NULL;
127
        FilterAction *action;
128
        GSList *cur;
129
        gchar *cmdline;
130
        gboolean copy_to_self = FALSE;
131

    
132
        g_return_val_if_fail(rule != NULL, -1);
133
        g_return_val_if_fail(msginfo != NULL, -1);
134
        g_return_val_if_fail(file != NULL, -1);
135
        g_return_val_if_fail(fltinfo != NULL, -1);
136

    
137
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
138
                action = (FilterAction *)cur->data;
139

    
140
                switch (action->type) {
141
                case FLT_ACTION_MARK:
142
                        debug_print("filter_action_exec(): mark\n");
143
                        MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED);
144
                        fltinfo->actions[action->type] = TRUE;
145
                        break;
146
                case FLT_ACTION_COLOR_LABEL:
147
                        debug_print("filter_action_exec(): color label: %d\n",
148
                                    action->int_value);
149
                        MSG_UNSET_PERM_FLAGS(fltinfo->flags,
150
                                             MSG_CLABEL_FLAG_MASK);
151
                        MSG_SET_COLORLABEL_VALUE(fltinfo->flags,
152
                                                 action->int_value);
153
                        fltinfo->actions[action->type] = TRUE;
154
                        break;
155
                case FLT_ACTION_MARK_READ:
156
                        debug_print("filter_action_exec(): mark as read\n");
157
                        if (msginfo->folder) {
158
                                if (MSG_IS_NEW(fltinfo->flags))
159
                                        msginfo->folder->new--;
160
                                if (MSG_IS_UNREAD(fltinfo->flags))
161
                                        msginfo->folder->unread--;
162
                        }
163
                        MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD);
164
                        fltinfo->actions[action->type] = TRUE;
165
                        break;
166
                case FLT_ACTION_EXEC:
167
                        cmdline = g_strconcat(action->str_value, " ", file,
168
                                              NULL);
169
                        execute_command_line(cmdline, FALSE);
170
                        g_free(cmdline);
171
                        fltinfo->actions[action->type] = TRUE;
172
                        break;
173
                case FLT_ACTION_EXEC_ASYNC:
174
                        cmdline = g_strconcat(action->str_value, " ", file,
175
                                              NULL);
176
                        execute_command_line(cmdline, TRUE);
177
                        g_free(cmdline);
178
                        fltinfo->actions[action->type] = TRUE;
179
                        break;
180
                default:
181
                        break;
182
                }
183
        }
184

    
185
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
186
                action = (FilterAction *)cur->data;
187

    
188
                switch (action->type) {
189
                case FLT_ACTION_MOVE:
190
                case FLT_ACTION_COPY:
191
                        dest_folder = folder_find_item_from_identifier
192
                                (action->str_value);
193
                        if (!dest_folder) {
194
                                g_warning("dest folder '%s' not found\n",
195
                                          action->str_value);
196
                                return -1;
197
                        }
198
                        debug_print("filter_action_exec(): %s: dest_folder = %s\n",
199
                                    action->type == FLT_ACTION_COPY ?
200
                                    "copy" : "move", action->str_value);
201

    
202
                        if (msginfo->folder) {
203
                                gint val;
204

    
205
                                /* local filtering */
206
                                if (msginfo->folder == dest_folder)
207
                                        copy_to_self = TRUE;
208
                                else {
209
                                        if (action->type == FLT_ACTION_COPY) {
210
                                                val = folder_item_copy_msg
211
                                                        (dest_folder, msginfo);
212
                                                if (val == -1)
213
                                                        return -1;
214
                                        }
215
                                        fltinfo->actions[action->type] = TRUE;
216
                                }
217
                        } else {
218
                                if (folder_item_add_msg(dest_folder, file,
219
                                                        &fltinfo->flags,
220
                                                        FALSE) < 0)
221
                                        return -1;
222
                                fltinfo->actions[action->type] = TRUE;
223
                        }
224

    
225
                        fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
226
                                                            dest_folder);
227
                        if (action->type == FLT_ACTION_MOVE) {
228
                                fltinfo->move_dest = dest_folder;
229
                                fltinfo->drop_done = TRUE;
230
                        }
231
                        break;
232
                default:
233
                        break;
234
                }
235
        }
236

    
237
        if (fltinfo->drop_done == TRUE)
238
                return 0;
239

    
240
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
241
                action = (FilterAction *)cur->data;
242

    
243
                switch (action->type) {
244
                case FLT_ACTION_NOT_RECEIVE:
245
                        debug_print("filter_action_exec(): don't receive\n");
246
                        fltinfo->drop_done = TRUE;
247
                        fltinfo->actions[action->type] = TRUE;
248
                        return 0;
249
                case FLT_ACTION_DELETE:
250
                        debug_print("filter_action_exec(): delete\n");
251
                        if (msginfo->folder) {
252
                                /* local filtering */
253
                                if (copy_to_self == FALSE)
254
                                        fltinfo->actions[action->type] = TRUE;
255
                        } else
256
                                fltinfo->actions[action->type] = TRUE;
257

    
258
                        fltinfo->drop_done = TRUE;
259
                        return 0;
260
                case FLT_ACTION_STOP_EVAL:
261
                        debug_print("filter_action_exec(): stop evaluation\n");
262
                        fltinfo->actions[action->type] = TRUE;
263
                        return 0;
264
                default:
265
                        break;
266
                }
267
        }
268

    
269
        return 0;
270
}
271

    
272
static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
273
{
274
        gint ret = 0;
275
        regex_t preg;
276
        regmatch_t pmatch[1];
277

    
278
        ret = regcomp(&preg, needle, REG_EXTENDED|REG_ICASE);
279
        if (ret != 0) return FALSE;
280

    
281
        ret = regexec(&preg, haystack, 1, pmatch, 0);
282
        regfree(&preg);
283

    
284
        if (ret == REG_NOMATCH) return FALSE;
285

    
286
        if (pmatch[0].rm_so != -1)
287
                return TRUE;
288
        else
289
                return FALSE;
290
}
291

    
292
gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist,
293
                           FilterInfo *fltinfo)
294
{
295
        FilterCond *cond;
296
        GSList *cur;
297
        gboolean matched;
298

    
299
        g_return_val_if_fail(rule->cond_list != NULL, FALSE);
300
        g_return_val_if_fail(rule->action_list != NULL, FALSE);
301

    
302
        switch (rule->timing) {
303
        case FLT_TIMING_ANY:
304
                break;
305
        case FLT_TIMING_ON_RECEIVE:
306
                if (msginfo->folder != NULL)
307
                        return FALSE;
308
                break;
309
        case FLT_TIMING_MANUAL:
310
                if (msginfo->folder == NULL)
311
                        return FALSE;
312
                break;
313
        default:
314
                break;
315
        }
316

    
317
        if (rule->bool_op == FLT_AND) {
318
                for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
319
                        cond = (FilterCond *)cur->data;
320
                        matched = filter_match_cond(cond, msginfo, hlist,
321
                                                    fltinfo);
322
                        if (matched == FALSE)
323
                                return FALSE;
324
                }
325

    
326
                return TRUE;
327
        } else if (rule->bool_op == FLT_OR) {
328
                for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
329
                        cond = (FilterCond *)cur->data;
330
                        matched = filter_match_cond(cond, msginfo, hlist,
331
                                                    fltinfo);
332
                        if (matched == TRUE)
333
                                return TRUE;
334
                }
335

    
336
                return FALSE;
337
        }
338

    
339
        return FALSE;
340
}
341

    
342
static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo,
343
                                  GSList *hlist, FilterInfo *fltinfo)
344
{
345
        gboolean matched = FALSE;
346
        gchar *file;
347
        gchar *cmdline;
348
        PrefsAccount *cond_ac;
349

    
350
        switch (cond->type) {
351
        case FLT_COND_HEADER:
352
        case FLT_COND_ANY_HEADER:
353
        case FLT_COND_TO_OR_CC:
354
                return filter_match_header_cond(cond, hlist);
355
        case FLT_COND_BODY:
356
                matched = procmime_find_string(msginfo, cond->str_value,
357
                                               cond->match_func);
358
                break;
359
        case FLT_COND_CMD_TEST:
360
                file = procmsg_get_message_file(msginfo);
361
                cmdline = g_strconcat(cond->str_value, " ", file, NULL);
362
                matched = (execute_command_line(cmdline, FALSE) == 0);
363
                g_free(cmdline);
364
                g_free(file);
365
                break;
366
        case FLT_COND_SIZE_GREATER:
367
                matched = (msginfo->size > cond->int_value * 1024);
368
                break;
369
        case FLT_COND_AGE_GREATER:
370
                matched = (time(NULL) - msginfo->date_t >
371
                                cond->int_value * 24 * 60 * 60);
372
                break;
373
        case FLT_COND_ACCOUNT:
374
                cond_ac = account_find_from_id(cond->int_value);
375
                matched = (cond_ac != NULL && cond_ac == fltinfo->account);
376
                break;
377
        default:
378
                g_warning("filter_match_cond(): unknown condition: %d\n",
379
                          cond->type);
380
                return FALSE;
381
        }
382

    
383
        if (FLT_IS_NOT_MATCH(cond->match_flag))
384
                matched = !matched;
385

    
386
        return matched;
387
}
388

    
389
static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist)
390
{
391
        gboolean matched = FALSE;
392
        GSList *cur;
393
        Header *header;
394

    
395
        for (cur = hlist; cur != NULL; cur = cur->next) {
396
                header = (Header *)cur->data;
397

    
398
                switch (cond->type) {
399
                case FLT_COND_HEADER:
400
                        if (!strcasecmp(header->name, cond->header_name)) {
401
                                if (!cond->str_value ||
402
                                    cond->match_func(header->body,
403
                                                     cond->str_value))
404
                                        matched = TRUE;
405
                        }
406
                        break;
407
                case FLT_COND_ANY_HEADER:
408
                        if (!cond->str_value ||
409
                            cond->match_func(header->body, cond->str_value))
410
                                matched = TRUE;
411
                        break;
412
                case FLT_COND_TO_OR_CC:
413
                        if (!strcasecmp(header->name, "To") ||
414
                            !strcasecmp(header->name, "Cc")) {
415
                                if (!cond->str_value ||
416
                                    cond->match_func(header->body,
417
                                                     cond->str_value))
418
                                        matched = TRUE;
419
                        }
420
                        break;
421
                default:
422
                        break;
423
                }
424

    
425
                if (matched == TRUE)
426
                        break;
427
        }
428

    
429
        if (FLT_IS_NOT_MATCH(cond->match_flag))
430
                matched = !matched;
431

    
432
        return matched;
433
}
434

    
435
#define RETURN_IF_TAG_NOT_MATCH(tag_name)                        \
436
        if (strcmp2(xmlnode->tag->tag, tag_name) != 0) {        \
437
                g_warning("tag name != \"" tag_name "\"\n");        \
438
                filter_cond_list_free(cond_list);                \
439
                filter_action_list_free(cond_list);                \
440
                return FALSE;                                        \
441
        }                                                        \
442

    
443
#define STR_SWITCH(sw)                { const gchar *sw_str = sw;
444
#define STR_CASE_BEGIN(str)        if (!strcmp(sw_str, str)) {
445
#define STR_CASE(str)                } else if (!strcmp(sw_str, str)) {
446
#define STR_CASE_END                } }
447

    
448
static gboolean filter_xml_node_func(GNode *node, gpointer data)
449
{
450
        XMLNode *xmlnode;
451
        GList *list;
452
        const gchar *name = NULL;
453
        FilterTiming timing = FLT_TIMING_ANY;
454
        gboolean enabled = TRUE;
455
        FilterRule *rule;
456
        FilterBoolOp bool_op = FLT_OR;
457
        GSList *cond_list = NULL;
458
        GSList *action_list = NULL;
459
        GNode *child, *cond_child, *action_child;
460
        GSList **fltlist = (GSList **)data;
461

    
462
        if (g_node_depth(node) != 2) return FALSE;
463
        g_return_val_if_fail(node->data != NULL, FALSE);
464

    
465
        xmlnode = node->data;
466
        RETURN_IF_TAG_NOT_MATCH("rule");
467

    
468
        for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
469
                XMLAttr *attr = (XMLAttr *)list->data;
470

    
471
                if (!attr || !attr->name || !attr->value) continue;
472
                if (!strcmp(attr->name, "name"))
473
                        name = attr->value;
474
                else if (!strcmp(attr->name, "timing")) {
475
                        if (!strcmp(attr->value, "any"))
476
                                timing = FLT_TIMING_ANY;
477
                        else if (!strcmp(attr->value, "receive"))
478
                                timing = FLT_TIMING_ON_RECEIVE;
479
                        else if (!strcmp(attr->value, "manual"))
480
                                timing = FLT_TIMING_MANUAL;
481
                } else if (!strcmp(attr->name, "enabled")) {
482
                        if (!strcmp(attr->value, "true"))
483
                                enabled = TRUE;
484
                        else
485
                                enabled = FALSE;
486
                }
487
        }
488

    
489
        /* condition list */
490
        child = node->children;
491
        if (!child) {
492
                g_warning("<rule> must have children\n");
493
                return FALSE;
494
        }
495
        xmlnode = child->data;
496
        RETURN_IF_TAG_NOT_MATCH("condition-list");
497
        for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
498
                XMLAttr *attr = (XMLAttr *)list->data;
499

    
500
                if (attr && attr->name && attr->value &&
501
                    !strcmp(attr->name, "bool")) {
502
                        if (!strcmp(attr->value, "or"))
503
                                bool_op = FLT_OR;
504
                        else
505
                                bool_op = FLT_AND;
506
                }
507
        }
508

    
509
        for (cond_child = child->children; cond_child != NULL;
510
             cond_child = cond_child->next) {
511
                const gchar *type = NULL;
512
                const gchar *name = NULL;
513
                const gchar *value = NULL;
514
                FilterCond *cond;
515
                FilterCondType cond_type = FLT_COND_HEADER;
516
                FilterMatchType match_type = FLT_CONTAIN;
517
                FilterMatchFlag match_flag = 0;
518

    
519
                xmlnode = cond_child->data;
520
                if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
521

    
522
                for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
523
                        XMLAttr *attr = (XMLAttr *)list->data;
524

    
525
                        if (!attr || !attr->name || !attr->value) continue;
526
                        if (!strcmp(attr->name, "type"))
527
                                type = attr->value;
528
                        else if (!strcmp(attr->name, "name"))
529
                                name = attr->value;
530
                }
531

    
532
                if (type) {
533
                        filter_rule_match_type_str_to_enum
534
                                (type, &match_type, &match_flag);
535
                }
536
                value = xmlnode->element;
537

    
538
                STR_SWITCH(xmlnode->tag->tag)
539
                STR_CASE_BEGIN("match-header")
540
                        cond_type = FLT_COND_HEADER;
541
                STR_CASE("match-any-header")
542
                        cond_type = FLT_COND_ANY_HEADER;
543
                STR_CASE("match-to-or-cc")
544
                        cond_type = FLT_COND_TO_OR_CC;
545
                STR_CASE("match-body-text")
546
                        cond_type = FLT_COND_BODY;
547
                STR_CASE("command-test")
548
                        cond_type = FLT_COND_CMD_TEST;
549
                STR_CASE("size")
550
                        cond_type = FLT_COND_SIZE_GREATER;
551
                STR_CASE("age")
552
                        cond_type = FLT_COND_AGE_GREATER;
553
                STR_CASE("account-id")
554
                        cond_type = FLT_COND_ACCOUNT;
555
                STR_CASE_END
556

    
557
                cond = filter_cond_new(cond_type, match_type, match_flag,
558
                                       name, value);
559
                cond_list = g_slist_append(cond_list, cond);
560
        }
561

    
562
        /* action list */
563
        child = child->next;
564
        if (!child) {
565
                g_warning("<rule> must have multiple children\n");
566
                filter_cond_list_free(cond_list);
567
                return FALSE;
568
        }
569
        xmlnode = child->data;
570
        RETURN_IF_TAG_NOT_MATCH("action-list");
571

    
572
        for (action_child = child->children; action_child != NULL;
573
             action_child = action_child->next) {
574
                FilterAction *action;
575
                FilterActionType action_type = FLT_ACTION_NONE;
576

    
577
                xmlnode = action_child->data;
578
                if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
579

    
580
                STR_SWITCH(xmlnode->tag->tag)
581
                STR_CASE_BEGIN("move")
582
                        action_type = FLT_ACTION_MOVE;
583
                STR_CASE("copy")
584
                        action_type = FLT_ACTION_COPY;
585
                STR_CASE("not-receive")
586
                        action_type = FLT_ACTION_NOT_RECEIVE;
587
                STR_CASE("delete")
588
                        action_type = FLT_ACTION_DELETE;
589
                STR_CASE("exec")
590
                        action_type = FLT_ACTION_EXEC;
591
                STR_CASE("exec-async")
592
                        action_type = FLT_ACTION_EXEC_ASYNC;
593
                STR_CASE("mark")
594
                        action_type = FLT_ACTION_MARK;
595
                STR_CASE("color-label")
596
                        action_type = FLT_ACTION_COLOR_LABEL;
597
                STR_CASE("mark-as-read")
598
                        action_type = FLT_ACTION_MARK_READ;
599
                STR_CASE("forward")
600
                        action_type = FLT_ACTION_FORWARD;
601
                STR_CASE("forward-as-attachment")
602
                        action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT;
603
                STR_CASE("redirect")
604
                        action_type = FLT_ACTION_REDIRECT;
605
                STR_CASE("stop-eval")
606
                        action_type = FLT_ACTION_STOP_EVAL;
607
                STR_CASE_END
608

    
609
                action = filter_action_new(action_type, xmlnode->element);
610
                action_list = g_slist_append(action_list, action);
611
        }
612

    
613
        if (name && cond_list && action_list) {
614
                rule = filter_rule_new(name, bool_op, cond_list, action_list);
615
                rule->timing = timing;
616
                rule->enabled = enabled;
617
                *fltlist = g_slist_prepend(*fltlist, rule);
618
        }
619

    
620
        return FALSE;
621
}
622

    
623
#undef RETURN_IF_TAG_NOT_MATCH
624
#undef STR_SWITCH
625
#undef STR_CASE_BEGIN
626
#undef STR_CASE
627
#undef STR_CASE_END
628

    
629
GSList *filter_xml_node_to_filter_list(GNode *node)
630
{
631
        GSList *fltlist = NULL;
632

    
633
        g_return_val_if_fail(node != NULL, NULL);
634

    
635
        g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
636
                        filter_xml_node_func, &fltlist);
637
        fltlist = g_slist_reverse(fltlist);
638

    
639
        return fltlist;
640
}
641

    
642
#define NODE_NEW(tag, text) \
643
        node = xml_node_new(xml_tag_new(tag), text)
644
#define ADD_ATTR(name, value) \
645
        xml_tag_add_attr(node->tag, xml_attr_new(name, value))
646

    
647
void filter_write_config(GSList *fltlist)
648
{
649
        gchar *rcpath;
650
        PrefFile *pfile;
651
        GSList *cur;
652

    
653
        debug_print("Writing filter configuration...\n");
654

    
655
        rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
656
                             NULL);
657
        if ((pfile = prefs_file_open(rcpath)) == NULL) {
658
                g_warning("failed to write filter configuration to file\n");
659
                g_free(rcpath);
660
                return;
661
        }
662

    
663
        xml_file_put_xml_decl(pfile->fp);
664
        fputs("\n<filter>\n", pfile->fp);
665

    
666
        for (cur = fltlist; cur != NULL; cur = cur->next) {
667
                FilterRule *rule = (FilterRule *)cur->data;
668
                GSList *cur_cond;
669
                GSList *cur_action;
670
                gchar match_type[16];
671

    
672
                fputs("    <rule name=\"", pfile->fp);
673
                xml_file_put_escape_str(pfile->fp, rule->name);
674
                fprintf(pfile->fp, "\" timing=\"%s\"",
675
                        rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" :
676
                        rule->timing == FLT_TIMING_MANUAL ? "manual" : "any");
677
                fprintf(pfile->fp, " enabled=\"%s\">\n",
678
                        rule->enabled ? "true" : "false");
679

    
680
                fprintf(pfile->fp, "        <condition-list bool=\"%s\">\n",
681
                        rule->bool_op == FLT_OR ? "or" : "and");
682

    
683
                for (cur_cond = rule->cond_list; cur_cond != NULL;
684
                     cur_cond = cur_cond->next) {
685
                        FilterCond *cond = (FilterCond *)cur_cond->data;
686
                        XMLNode *node = NULL;
687

    
688
                        switch (cond->match_type) {
689
                        case FLT_CONTAIN:
690
                                strcpy(match_type,
691
                                       FLT_IS_NOT_MATCH(cond->match_flag)
692
                                       ? "not-contain" : "contains");
693
                                break;
694
                        case FLT_EQUAL:
695
                                strcpy(match_type,
696
                                       FLT_IS_NOT_MATCH(cond->match_flag)
697
                                       ? "is-not" : "is");
698
                                break;
699
                        case FLT_REGEX:
700
                                strcpy(match_type,
701
                                       FLT_IS_NOT_MATCH(cond->match_flag)
702
                                       ? "not-regex" : "regex");
703
                                break;
704
                        default:
705
                                match_type[0] = '\0';
706
                                break;
707
                        }
708

    
709
                        switch (cond->type) {
710
                        case FLT_COND_HEADER:
711
                                NODE_NEW("match-header", cond->str_value);
712
                                ADD_ATTR("type", match_type);
713
                                ADD_ATTR("name", cond->header_name);
714
                                break;
715
                        case FLT_COND_ANY_HEADER:
716
                                NODE_NEW("match-any-header", cond->str_value);
717
                                ADD_ATTR("type", match_type);
718
                                break;
719
                        case FLT_COND_TO_OR_CC:
720
                                NODE_NEW("match-to-or-cc", cond->str_value);
721
                                ADD_ATTR("type", match_type);
722
                                break;
723
                        case FLT_COND_BODY:
724
                                NODE_NEW("match-body-text", cond->str_value);
725
                                ADD_ATTR("type", match_type);
726
                                break;
727
                        case FLT_COND_CMD_TEST:
728
                                NODE_NEW("command-test", cond->str_value);
729
                                break;
730
                        case FLT_COND_SIZE_GREATER:
731
                                NODE_NEW("size", itos(cond->int_value));
732
                                ADD_ATTR("type",
733
                                         FLT_IS_NOT_MATCH(cond->match_flag)
734
                                         ? "lt" : "gt");
735
                                break;
736
                        case FLT_COND_AGE_GREATER:
737
                                NODE_NEW("age", itos(cond->int_value));
738
                                ADD_ATTR("type",
739
                                         FLT_IS_NOT_MATCH(cond->match_flag)
740
                                         ? "lt" : "gt");
741
                                break;
742
                        case FLT_COND_ACCOUNT:
743
                                 NODE_NEW("account-id", itos(cond->int_value));
744
                                 break;
745
                        default:
746
                                 break;
747
                        }
748

    
749
                        if (node) {
750
                                fputs("            ", pfile->fp);
751
                                xml_file_put_node(pfile->fp, node);
752
                                xml_free_node(node);
753
                        }
754
                }
755

    
756
                fputs("        </condition-list>\n", pfile->fp);
757

    
758
                fputs("        <action-list>\n", pfile->fp);
759

    
760
                for (cur_action = rule->action_list; cur_action != NULL;
761
                     cur_action = cur_action->next) {
762
                        FilterAction *action = (FilterAction *)cur_action->data;
763
                        XMLNode *node = NULL;
764

    
765
                        switch (action->type) {
766
                        case FLT_ACTION_MOVE:
767
                                NODE_NEW("move", action->str_value);
768
                                break;
769
                        case FLT_ACTION_COPY:
770
                                NODE_NEW("copy", action->str_value);
771
                                break;
772
                        case FLT_ACTION_NOT_RECEIVE:
773
                                NODE_NEW("not-receive", NULL);
774
                                break;
775
                        case FLT_ACTION_DELETE:
776
                                NODE_NEW("delete", NULL);
777
                                break;
778
                        case FLT_ACTION_EXEC:
779
                                NODE_NEW("exec", action->str_value);
780
                                break;
781
                        case FLT_ACTION_EXEC_ASYNC:
782
                                NODE_NEW("exec-async", action->str_value);
783
                                break;
784
                        case FLT_ACTION_MARK:
785
                                NODE_NEW("mark", NULL);
786
                                break;
787
                        case FLT_ACTION_COLOR_LABEL:
788
                                NODE_NEW("color-label", action->str_value);
789
                                break;
790
                        case FLT_ACTION_MARK_READ:
791
                                NODE_NEW("mark-as-read", NULL);
792
                                break;
793
                        case FLT_ACTION_FORWARD:
794
                                NODE_NEW("forward", action->str_value);
795
                                break;
796
                        case FLT_ACTION_FORWARD_AS_ATTACHMENT:
797
                                NODE_NEW("forward-as-attachment",
798
                                         action->str_value);
799
                                break;
800
                        case FLT_ACTION_REDIRECT:
801
                                NODE_NEW("redirect", action->str_value);
802
                                break;
803
                        case FLT_ACTION_STOP_EVAL:
804
                                NODE_NEW("stop-eval", NULL);
805
                                break;
806
                        default:
807
                                break;
808
                        }
809

    
810
                        if (node) {
811
                                fputs("            ", pfile->fp);
812
                                xml_file_put_node(pfile->fp, node);
813
                                xml_free_node(node);
814
                        }
815
                }
816

    
817
                fputs("        </action-list>\n", pfile->fp);
818

    
819
                fputs("    </rule>\n", pfile->fp);
820
        }
821

    
822
        fputs("</filter>\n", pfile->fp);
823

    
824
        g_free(rcpath);
825

    
826
        if (prefs_file_close(pfile) < 0) {
827
                g_warning(_("failed to write configuration to file\n"));
828
                return;
829
        }
830
}
831

    
832
#undef NODE_NEW
833
#undef ADD_ATTR
834

    
835
gchar *filter_get_str(FilterRule *rule)
836
{
837
        gchar *str;
838
        FilterCond *cond1, *cond2;
839
        FilterAction *action = NULL;
840
        GSList *cur;
841
        gint flag1 = 0, flag2 = 0;
842

    
843
        cond1 = (FilterCond *)rule->cond_list->data;
844
        if (rule->cond_list->next)
845
                cond2 = (FilterCond *)rule->cond_list->next->data;
846
        else
847
                cond2 = NULL;
848

    
849
        /* new -> old flag conversion */
850
        switch (cond1->match_type) {
851
        case FLT_CONTAIN:
852
        case FLT_EQUAL:
853
                flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN;
854
                if (FLT_IS_CASE_SENS(cond1->match_flag))
855
                        flag1 |= FLT_O_CASE_SENS;
856
                break;
857
        case FLT_REGEX:
858
                flag1 = FLT_O_REGEX; break;
859
        default:
860
                break;
861
        }
862
        if (cond2) {
863
                switch (cond2->match_type) {
864
                case FLT_CONTAIN:
865
                case FLT_EQUAL:
866
                        flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN;
867
                        if (FLT_IS_CASE_SENS(cond2->match_flag))
868
                                flag2 |= FLT_O_CASE_SENS;
869
                        break;
870
                case FLT_REGEX:
871
                        flag2 = FLT_O_REGEX; break;
872
                default:
873
                        break;
874
                }
875
        } else
876
                flag2 = FLT_O_CONTAIN;
877

    
878
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
879
                action = (FilterAction *)cur->data;
880
                if (action->type == FLT_ACTION_MOVE ||
881
                    action->type == FLT_ACTION_NOT_RECEIVE ||
882
                    action->type == FLT_ACTION_DELETE)
883
                        break;
884
        }
885

    
886
        str = g_strdup_printf
887
                ("%s:%s:%c:%s:%s:%s:%d:%d:%c",
888
                 cond1->header_name, cond1->str_value ? cond1->str_value : "",
889
                 (cond2 && cond2->header_name) ?
890
                         (rule->bool_op == FLT_AND ? '&' : '|') : ' ',
891
                 (cond2 && cond2->header_name) ? cond2->header_name : "",
892
                 (cond2 && cond2->str_value) ? cond2->str_value : "",
893
                 (action && action->str_value) ? action->str_value : "",
894
                 flag1, flag2,
895
                 (action && action->type == FLT_ACTION_MOVE) ? 'm' :
896
                 (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' :
897
                 (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' ');
898

    
899
        return str;
900
}
901

    
902
#define PARSE_ONE_PARAM(p, srcp) \
903
{ \
904
        p = strchr(srcp, '\t'); \
905
        if (!p) return NULL; \
906
        else \
907
                *p++ = '\0'; \
908
}
909

    
910
FilterRule *filter_read_str(const gchar *str)
911
{
912
        FilterRule *rule;
913
        FilterBoolOp bool_op;
914
        gint flag;
915
        FilterCond *cond;
916
        FilterMatchType match_type;
917
        FilterMatchFlag match_flag;
918
        FilterAction *action;
919
        GSList *cond_list = NULL;
920
        GSList *action_list = NULL;
921
        gchar *tmp;
922
        gchar *rule_name;
923
        gchar *name1, *body1, *op, *name2, *body2, *dest;
924
        gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL;
925

    
926
        Xstrdup_a(tmp, str, return NULL);
927

    
928
        name1 = tmp;
929
        PARSE_ONE_PARAM(body1, name1);
930
        PARSE_ONE_PARAM(op, body1);
931
        PARSE_ONE_PARAM(name2, op);
932
        PARSE_ONE_PARAM(body2, name2);
933
        PARSE_ONE_PARAM(dest, body2);
934
        if (strchr(dest, '\t')) {
935
                gchar *p;
936

    
937
                PARSE_ONE_PARAM(flag1, dest);
938
                PARSE_ONE_PARAM(flag2, flag1);
939
                PARSE_ONE_PARAM(action1, flag2);
940
                if ((p = strchr(action1, '\t'))) *p = '\0';
941
        }
942

    
943
        bool_op = (*op == '&') ? FLT_AND : FLT_OR;
944

    
945
        if (*name1) {
946
                if (flag1)
947
                        flag = (FilterOldFlag)strtoul(flag1, NULL, 10);
948
                else
949
                        flag = FLT_O_CONTAIN;
950
                match_type = FLT_CONTAIN;
951
                match_flag = 0;
952
                if (flag & FLT_O_REGEX)
953
                        match_type = FLT_REGEX;
954
                else if (!(flag & FLT_O_CONTAIN))
955
                        match_flag = FLT_NOT_MATCH;
956
                if (flag & FLT_O_CASE_SENS)
957
                        match_flag |= FLT_CASE_SENS;
958
                cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
959
                                       name1, body1);
960
                cond_list = g_slist_append(cond_list, cond);
961
        }
962
        if (*name2) {
963
                if (flag2)
964
                        flag = (FilterOldFlag)strtoul(flag2, NULL, 10);
965
                else
966
                        flag = FLT_O_CONTAIN;
967
                match_type = FLT_CONTAIN;
968
                match_flag = 0;
969
                if (flag & FLT_O_REGEX)
970
                        match_type = FLT_REGEX;
971
                else if (!(flag & FLT_O_CONTAIN))
972
                        match_flag = FLT_NOT_MATCH;
973
                if (flag & FLT_O_CASE_SENS)
974
                        match_flag |= FLT_CASE_SENS;
975
                cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
976
                                       name2, body2);
977
                cond_list = g_slist_append(cond_list, cond);
978
        }
979

    
980
        action = filter_action_new(FLT_ACTION_MOVE,
981
                                   *dest ? g_strdup(dest) : NULL);
982
        if (action1) {
983
                switch (*action1) {
984
                case 'm': action->type = FLT_ACTION_MOVE;                break;
985
                case 'n': action->type = FLT_ACTION_NOT_RECEIVE;        break;
986
                case 'd': action->type = FLT_ACTION_DELETE;                break;
987
                default:  g_warning("Invalid action: `%c'\n", *action1);
988
                }
989
        }
990
        action_list = g_slist_append(action_list, action);
991

    
992
        Xstrdup_a(rule_name, str, return NULL);
993
        subst_char(rule_name, '\t', ':');
994

    
995
        rule = filter_rule_new(rule_name, bool_op, cond_list, action_list);
996

    
997
        return rule;
998
}
999

    
1000
FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op,
1001
                            GSList *cond_list, GSList *action_list)
1002
{
1003
        FilterRule *rule;
1004

    
1005
        rule = g_new0(FilterRule, 1);
1006
        rule->name = g_strdup(name);
1007
        rule->bool_op = bool_op;
1008
        rule->cond_list = cond_list;
1009
        rule->action_list = action_list;
1010
        rule->timing = FLT_TIMING_ANY;
1011
        rule->enabled = TRUE;
1012

    
1013
        return rule;
1014
}
1015

    
1016
FilterCond *filter_cond_new(FilterCondType type,
1017
                            FilterMatchType match_type,
1018
                            FilterMatchFlag match_flag,
1019
                            const gchar *header, const gchar *value)
1020
{
1021
        FilterCond *cond;
1022

    
1023
        cond = g_new0(FilterCond, 1);
1024
        cond->type = type;
1025
        cond->match_type = match_type;
1026
        cond->match_flag = match_flag;
1027

    
1028
        if (type == FLT_COND_HEADER)
1029
                cond->header_name =
1030
                        (header && *header) ? g_strdup(header) : NULL;
1031
        else
1032
                cond->header_name = NULL;
1033

    
1034
        cond->str_value = (value && *value) ? g_strdup(value) : NULL;
1035
        if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER)
1036
                cond->int_value = atoi(value);
1037
        else
1038
                cond->int_value = 0;
1039

    
1040
        if (match_type == FLT_REGEX)
1041
                cond->match_func = strmatch_regex;
1042
        else if (match_type == FLT_EQUAL) {
1043
                if (FLT_IS_CASE_SENS(match_flag))
1044
                        cond->match_func = str_find_equal;
1045
                else
1046
                        cond->match_func = str_case_find_equal;
1047
        } else {
1048
                if (FLT_IS_CASE_SENS(match_flag))
1049
                        cond->match_func = str_find;
1050
                else
1051
                        cond->match_func = str_case_find;
1052
        }
1053

    
1054
        return cond;
1055
}
1056

    
1057
FilterAction *filter_action_new(FilterActionType type, const gchar *str)
1058
{
1059
        FilterAction *action;
1060

    
1061
        action = g_new0(FilterAction, 1);
1062
        action->type = type;
1063

    
1064
        action->str_value = (str && *str) ? g_strdup(str) : NULL;
1065
        if (type == FLT_ACTION_COLOR_LABEL && str)
1066
                action->int_value = atoi(str);
1067
        else
1068
                action->int_value = 0;
1069

    
1070
        return action;
1071
}
1072

    
1073
FilterInfo *filter_info_new(void)
1074
{
1075
        FilterInfo *fltinfo;
1076

    
1077
        fltinfo = g_new0(FilterInfo, 1);
1078
        fltinfo->dest_list = NULL;
1079
        fltinfo->move_dest = NULL;
1080
        fltinfo->drop_done = FALSE;
1081

    
1082
        return fltinfo;
1083
}
1084

    
1085
void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path,
1086
                                  const gchar *new_path)
1087
{
1088
        FilterAction *action;
1089
        GSList *cur;
1090
        gchar *base;
1091
        gchar *dest_path;
1092
        gint oldpathlen;
1093

    
1094
        for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1095
                action = (FilterAction *)cur->data;
1096

    
1097
                if (action->type != FLT_ACTION_MOVE &&
1098
                    action->type != FLT_ACTION_COPY)
1099
                        continue;
1100

    
1101
                oldpathlen = strlen(old_path);
1102
                if (action->str_value &&
1103
                    !strncmp(old_path, action->str_value, oldpathlen)) {
1104
                        base = action->str_value + oldpathlen;
1105
                        while (*base == G_DIR_SEPARATOR) base++;
1106
                        if (*base == '\0')
1107
                                dest_path = g_strdup(new_path);
1108
                        else
1109
                                dest_path = g_strconcat(new_path,
1110
                                                        G_DIR_SEPARATOR_S,
1111
                                                        base, NULL);
1112
                        g_free(action->str_value);
1113
                        action->str_value = dest_path;
1114
                }
1115
        }
1116
}
1117

    
1118
void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path)
1119
{
1120
        FilterAction *action;
1121
        GSList *cur;
1122
        GSList *next;
1123

    
1124
        for (cur = rule->action_list; cur != NULL; cur = next) {
1125
                action = (FilterAction *)cur->data;
1126
                next = cur->next;
1127

    
1128
                if (action->type != FLT_ACTION_MOVE &&
1129
                    action->type != FLT_ACTION_COPY)
1130
                        continue;
1131

    
1132
                if (action->str_value &&
1133
                    !strncmp(path, action->str_value, strlen(path))) {
1134
                        rule->action_list = g_slist_remove
1135
                                (rule->action_list, action);
1136
                        filter_action_free(action);
1137
                }
1138
        }
1139
}
1140

    
1141
void filter_rule_match_type_str_to_enum(const gchar *match_type,
1142
                                        FilterMatchType *type,
1143
                                        FilterMatchFlag *flag)
1144
{
1145
        g_return_if_fail(match_type != NULL);
1146

    
1147
        *type = FLT_CONTAIN;
1148
        *flag = 0;
1149

    
1150
        if (!strcmp(match_type, "contains")) {
1151
                *type = FLT_CONTAIN;
1152
        } else if (!strcmp(match_type, "not-contain")) {
1153
                *type = FLT_CONTAIN;
1154
                *flag = FLT_NOT_MATCH;
1155
        } else if (!strcmp(match_type, "is")) {
1156
                *type = FLT_EQUAL;
1157
        } else if (!strcmp(match_type, "is-not")) {
1158
                *type = FLT_EQUAL;
1159
                *flag = FLT_NOT_MATCH;
1160
        } else if (!strcmp(match_type, "regex")) {
1161
                *type = FLT_REGEX;
1162
        } else if (!strcmp(match_type, "not-regex")) {
1163
                *type = FLT_REGEX;
1164
                *flag = FLT_NOT_MATCH;
1165
        } else if (!strcmp(match_type, "gt")) {
1166
        } else if (!strcmp(match_type, "lt")) {
1167
                *flag = FLT_NOT_MATCH;
1168
        }
1169
}
1170

    
1171
void filter_rule_free(FilterRule *rule)
1172
{
1173
        if (!rule) return;
1174

    
1175
        g_free(rule->name);
1176

    
1177
        filter_cond_list_free(rule->cond_list);
1178
        filter_action_list_free(rule->action_list);
1179

    
1180
        g_free(rule);
1181
}
1182

    
1183
void filter_cond_list_free(GSList *cond_list)
1184
{
1185
        GSList *cur;
1186

    
1187
        for (cur = cond_list; cur != NULL; cur = cur->next)
1188
                filter_cond_free((FilterCond *)cur->data);
1189
        g_slist_free(cond_list);
1190
}
1191

    
1192
void filter_action_list_free(GSList *action_list)
1193
{
1194
        GSList *cur;
1195

    
1196
        for (cur = action_list; cur != NULL; cur = cur->next)
1197
                filter_action_free((FilterAction *)cur->data);
1198
        g_slist_free(action_list);
1199
}
1200

    
1201
static void filter_cond_free(FilterCond *cond)
1202
{
1203
        g_free(cond->header_name);
1204
        g_free(cond->str_value);
1205
        g_free(cond);
1206
}
1207

    
1208
static void filter_action_free(FilterAction *action)
1209
{
1210
        g_free(action->str_value);
1211
        g_free(action);
1212
}
1213

    
1214
void filter_info_free(FilterInfo *fltinfo)
1215
{
1216
        g_slist_free(fltinfo->dest_list);
1217
        g_free(fltinfo);
1218
}