Update the sort arrow in summary view column titles.
[claws.git] / src / matcher.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002 by the Sylpheed Claws Team and 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 #include <ctype.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <errno.h>
24 #include "defs.h"
25 #include "utils.h"
26 #include "procheader.h"
27 #include "matcher.h"
28 #include "intl.h"
29 #include "matcher_parser.h"
30 #include "prefs_gtk.h"
31 #include <ctype.h>
32
33 /*!
34  *\brief        Keyword lookup element
35  */
36 struct _MatchParser {
37         gint id;                /*!< keyword id */ 
38         gchar *str;             /*!< keyword */
39 };
40 typedef struct _MatchParser MatchParser;
41
42 /*!
43  *\brief        Table with strings and ids used by the lexer and
44  *              the parser. New keywords can be added here.
45  */
46 static const MatchParser matchparser_tab[] = {
47         /* msginfo flags */
48         {MATCHCRITERIA_ALL, "all"},
49         {MATCHCRITERIA_UNREAD, "unread"},
50         {MATCHCRITERIA_NOT_UNREAD, "~unread"},
51         {MATCHCRITERIA_NEW, "new"},
52         {MATCHCRITERIA_NOT_NEW, "~new"},
53         {MATCHCRITERIA_MARKED, "marked"},
54         {MATCHCRITERIA_NOT_MARKED, "~marked"},
55         {MATCHCRITERIA_DELETED, "deleted"},
56         {MATCHCRITERIA_NOT_DELETED, "~deleted"},
57         {MATCHCRITERIA_REPLIED, "replied"},
58         {MATCHCRITERIA_NOT_REPLIED, "~replied"},
59         {MATCHCRITERIA_FORWARDED, "forwarded"},
60         {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
61         {MATCHCRITERIA_LOCKED, "locked"},
62         {MATCHCRITERIA_NOT_LOCKED, "~locked"},
63         {MATCHCRITERIA_COLORLABEL, "colorlabel"},
64         {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
65         {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
66         {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
67
68         /* msginfo headers */
69         {MATCHCRITERIA_SUBJECT, "subject"},
70         {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
71         {MATCHCRITERIA_FROM, "from"},
72         {MATCHCRITERIA_NOT_FROM, "~from"},
73         {MATCHCRITERIA_TO, "to"},
74         {MATCHCRITERIA_NOT_TO, "~to"},
75         {MATCHCRITERIA_CC, "cc"},
76         {MATCHCRITERIA_NOT_CC, "~cc"},
77         {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
78         {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
79         {MATCHCRITERIA_AGE_GREATER, "age_greater"},
80         {MATCHCRITERIA_AGE_LOWER, "age_lower"},
81         {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
82         {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
83         {MATCHCRITERIA_INREPLYTO, "inreplyto"},
84         {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
85         {MATCHCRITERIA_REFERENCES, "references"},
86         {MATCHCRITERIA_NOT_REFERENCES, "~references"},
87         {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
88         {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
89         {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
90
91         {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
92         {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
93         {MATCHCRITERIA_SIZE_EQUAL,   "size_equal"},
94
95         /* content have to be read */
96         {MATCHCRITERIA_HEADER, "header"},
97         {MATCHCRITERIA_NOT_HEADER, "~header"},
98         {MATCHCRITERIA_HEADERS_PART, "headers_part"},
99         {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
100         {MATCHCRITERIA_MESSAGE, "message"},
101         {MATCHCRITERIA_NOT_MESSAGE, "~message"},
102         {MATCHCRITERIA_BODY_PART, "body_part"},
103         {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
104         {MATCHCRITERIA_TEST, "test"},
105         {MATCHCRITERIA_NOT_TEST, "~test"},
106
107         /* match type */
108         {MATCHTYPE_MATCHCASE, "matchcase"},
109         {MATCHTYPE_MATCH, "match"},
110         {MATCHTYPE_REGEXPCASE, "regexpcase"},
111         {MATCHTYPE_REGEXP, "regexp"},
112
113         /* actions */
114         {MATCHACTION_SCORE, "score"},    /* for backward compatibility */
115         {MATCHACTION_MOVE, "move"},
116         {MATCHACTION_COPY, "copy"},
117         {MATCHACTION_DELETE, "delete"},
118         {MATCHACTION_MARK, "mark"},
119         {MATCHACTION_UNMARK, "unmark"},
120         {MATCHACTION_LOCK, "lock"},
121         {MATCHACTION_UNLOCK, "unlock"},
122         {MATCHACTION_MARK_AS_READ, "mark_as_read"},
123         {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
124         {MATCHACTION_FORWARD, "forward"},
125         {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
126         {MATCHACTION_EXECUTE, "execute"},
127         {MATCHACTION_COLOR, "color"},
128         {MATCHACTION_REDIRECT, "redirect"},
129         {MATCHACTION_ADD_SCORE, "add_score"},
130         {MATCHACTION_SET_SCORE, "set_score"},
131         {MATCHACTION_STOP, "stop"},
132         {MATCHACTION_HIDE, "hide"},
133 };
134
135 /*!
136  *\brief        Look up table with keywords defined in \sa matchparser_tab
137  */
138 static GHashTable *matchparser_hashtab;
139
140 /*!
141  *\brief        Translate keyword id to keyword string
142  *
143  *\param        id Id of keyword
144  *
145  *\return       const gchar * Keyword
146  */
147 const gchar *get_matchparser_tab_str(gint id)
148 {
149         gint i;
150
151         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
152                 if (matchparser_tab[i].id == id)
153                         return matchparser_tab[i].str;
154         }
155         return NULL;
156 }
157
158 /*!
159  *\brief        Create keyword lookup table
160  */
161 static void create_matchparser_hashtab(void)
162 {
163         int i;
164         
165         if (matchparser_hashtab) return;
166         matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
167         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
168                 g_hash_table_insert(matchparser_hashtab,
169                                     matchparser_tab[i].str,
170                                     (gpointer) &matchparser_tab[i]);
171 }
172
173 /*!
174  *\brief        Return a keyword id from a keyword string
175  *
176  *\param        str Keyword string
177  *
178  *\return       gint Keyword id
179  */
180 gint get_matchparser_tab_id(const gchar *str)
181 {
182         MatchParser *res;
183
184         if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
185                 return res->id;
186         } else
187                 return -1;
188 }
189
190 /* **************** data structure allocation **************** */
191
192 /*!
193  *\brief        Allocate a structure for a filtering / scoring
194  *              "condition" (a matcher structure)
195  *
196  *\param        criteria Criteria ID (MATCHCRITERIA_XXXX)
197  *\param        header Header string (if criteria is MATCHCRITERIA_HEADER)
198  *\param        matchtype Type of action (MATCHTYPE_XXX)
199  *\param        expr String value or expression to check
200  *\param        value Integer value to check
201  *
202  *\return       MatcherProp * Pointer to newly allocated structure
203  */
204 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
205                               gint matchtype, const gchar *expr,
206                               int value)
207 {
208         MatcherProp *prop;
209
210         prop = g_new0(MatcherProp, 1);
211         prop->criteria = criteria;
212         prop->header = header != NULL ? g_strdup(header) : NULL;
213         prop->expr = expr != NULL ? g_strdup(expr) : NULL;
214         prop->matchtype = matchtype;
215         prop->preg = NULL;
216         prop->value = value;
217         prop->error = 0;
218
219         return prop;
220 }
221
222 /*!
223  *\brief        Free a matcher structure
224  *
225  *\param        prop Pointer to matcher structure allocated with
226  *              #matcherprop_new
227  */
228 void matcherprop_free(MatcherProp *prop)
229 {
230         if (prop->expr) 
231                 g_free(prop->expr);
232         if (prop->header)
233                 g_free(prop->header);
234         if (prop->preg != NULL) {
235                 regfree(prop->preg);
236                 g_free(prop->preg);
237         }
238         g_free(prop);
239 }
240
241 /*!
242  *\brief        Copy a matcher structure
243  *
244  *\param        src Matcher structure to copy
245  *
246  *\return       MatcherProp * Pointer to newly allocated matcher structure
247  */
248 MatcherProp *matcherprop_copy(const MatcherProp *src)
249 {
250         MatcherProp *prop = g_new0(MatcherProp, 1);
251         
252         prop->criteria = src->criteria;
253         prop->header = src->header ? g_strdup(src->header) : NULL;
254         prop->expr = src->expr ? g_strdup(src->expr) : NULL;
255         prop->matchtype = src->matchtype;
256         
257         prop->preg = NULL; /* will be re-evaluated */
258         prop->value = src->value;
259         prop->error = src->error;       
260         return prop;            
261 }
262
263 /* ************** match ******************************/
264
265 /*!
266  *\brief        Find out if a string matches a condition
267  *
268  *\param        prop Matcher structure
269  *\param        str String to check 
270  *
271  *\return       gboolean TRUE if str matches the condition in the 
272  *              matcher structure
273  */
274 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str)
275 {
276         gchar *str1;
277         gchar *str2;
278
279         if (str == NULL)
280                 return FALSE;
281
282         switch (prop->matchtype) {
283         case MATCHTYPE_REGEXPCASE:
284         case MATCHTYPE_REGEXP:
285                 if (!prop->preg && (prop->error == 0)) {
286                         prop->preg = g_new0(regex_t, 1);
287                         /* if regexp then don't use the escaped string */
288                         if (regcomp(prop->preg, prop->expr,
289                                     REG_NOSUB | REG_EXTENDED
290                                     | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
291                                     ? REG_ICASE : 0)) != 0) {
292                                 prop->error = 1;
293                                 g_free(prop->preg);
294                                 prop->preg = NULL;
295                         }
296                 }
297                 if (prop->preg == NULL)
298                         return FALSE;
299                 
300                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
301                         return TRUE;
302                 else
303                         return FALSE;
304
305         case MATCHTYPE_MATCH:
306                 return (strstr(str, prop->expr) != NULL);
307
308         /* FIXME: put upper in unesc_str */
309         case MATCHTYPE_MATCHCASE:
310                 str2 = alloca(strlen(prop->expr) + 1);
311                 strcpy(str2, prop->expr);
312                 g_strup(str2);
313                 str1 = alloca(strlen(str) + 1);
314                 strcpy(str1, str);
315                 g_strup(str1);
316                 return (strstr(str1, str2) != NULL);
317                 
318         default:
319                 return FALSE;
320         }
321 }
322
323 /*!
324  *\brief        Execute a command defined in the matcher structure
325  *
326  *\param        prop Pointer to matcher structure
327  *\param        info Pointer to message info structure
328  *
329  *\return       gboolean TRUE if command was executed succesfully
330  */
331 static gboolean matcherprop_match_test(const MatcherProp *prop, 
332                                           MsgInfo *info)
333 {
334         gchar *file;
335         gchar *cmd;
336         gint retval;
337
338         file = procmsg_get_message_file(info);
339         if (file == NULL)
340                 return FALSE;
341         g_free(file);           
342
343         cmd = matching_build_command(prop->expr, info);
344         if (cmd == NULL)
345                 return FALSE;
346
347         retval = system(cmd);
348         debug_print("Command exit code: %d\n", retval);
349
350         g_free(cmd);
351         return (retval == 0);
352 }
353
354 /*!
355  *\brief        Check if a message matches the condition in a matcher
356  *              structure.
357  *
358  *\param        prop Pointer to matcher structure
359  *\param        info Pointer to message info
360  *
361  *\return       gboolean TRUE if a match
362  */
363 gboolean matcherprop_match(MatcherProp *prop, 
364                            MsgInfo *info)
365 {
366         time_t t;
367
368         switch(prop->criteria) {
369         case MATCHCRITERIA_ALL:
370                 return 1;
371         case MATCHCRITERIA_UNREAD:
372                 return MSG_IS_UNREAD(info->flags);
373         case MATCHCRITERIA_NOT_UNREAD:
374                 return !MSG_IS_UNREAD(info->flags);
375         case MATCHCRITERIA_NEW:
376                 return MSG_IS_NEW(info->flags);
377         case MATCHCRITERIA_NOT_NEW:
378                 return !MSG_IS_NEW(info->flags);
379         case MATCHCRITERIA_MARKED:
380                 return MSG_IS_MARKED(info->flags);
381         case MATCHCRITERIA_NOT_MARKED:
382                 return !MSG_IS_MARKED(info->flags);
383         case MATCHCRITERIA_DELETED:
384                 return MSG_IS_DELETED(info->flags);
385         case MATCHCRITERIA_NOT_DELETED:
386                 return !MSG_IS_DELETED(info->flags);
387         case MATCHCRITERIA_REPLIED:
388                 return MSG_IS_REPLIED(info->flags);
389         case MATCHCRITERIA_NOT_REPLIED:
390                 return !MSG_IS_REPLIED(info->flags);
391         case MATCHCRITERIA_FORWARDED:
392                 return MSG_IS_FORWARDED(info->flags);
393         case MATCHCRITERIA_NOT_FORWARDED:
394                 return !MSG_IS_FORWARDED(info->flags);
395         case MATCHCRITERIA_LOCKED:
396                 return MSG_IS_LOCKED(info->flags);
397         case MATCHCRITERIA_NOT_LOCKED:
398                 return !MSG_IS_LOCKED(info->flags);
399         case MATCHCRITERIA_COLORLABEL:
400                 return MSG_GET_COLORLABEL_VALUE(info->flags) == prop->value; 
401         case MATCHCRITERIA_NOT_COLORLABEL:
402                 return MSG_GET_COLORLABEL_VALUE(info->flags) != prop->value;
403         case MATCHCRITERIA_IGNORE_THREAD:
404                 return MSG_IS_IGNORE_THREAD(info->flags);
405         case MATCHCRITERIA_NOT_IGNORE_THREAD:
406                 return !MSG_IS_IGNORE_THREAD(info->flags);
407         case MATCHCRITERIA_SUBJECT:
408                 return matcherprop_string_match(prop, info->subject);
409         case MATCHCRITERIA_NOT_SUBJECT:
410                 return !matcherprop_string_match(prop, info->subject);
411         case MATCHCRITERIA_FROM:
412                 return matcherprop_string_match(prop, info->from);
413         case MATCHCRITERIA_NOT_FROM:
414                 return !matcherprop_string_match(prop, info->from);
415         case MATCHCRITERIA_TO:
416                 return matcherprop_string_match(prop, info->to);
417         case MATCHCRITERIA_NOT_TO:
418                 return !matcherprop_string_match(prop, info->to);
419         case MATCHCRITERIA_CC:
420                 return matcherprop_string_match(prop, info->cc);
421         case MATCHCRITERIA_NOT_CC:
422                 return !matcherprop_string_match(prop, info->cc);
423         case MATCHCRITERIA_TO_OR_CC:
424                 return matcherprop_string_match(prop, info->to)
425                         || matcherprop_string_match(prop, info->cc);
426         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
427                 return !(matcherprop_string_match(prop, info->to)
428                 || matcherprop_string_match(prop, info->cc));
429         case MATCHCRITERIA_AGE_GREATER:
430                 t = time(NULL);
431                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
432         case MATCHCRITERIA_AGE_LOWER:
433                 t = time(NULL);
434                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
435         case MATCHCRITERIA_SCORE_GREATER:
436                 return info->score >= prop->value;
437         case MATCHCRITERIA_SCORE_LOWER:
438                 return info->score <= prop->value;
439         case MATCHCRITERIA_SCORE_EQUAL:
440                 return info->score == prop->value;
441         case MATCHCRITERIA_SIZE_GREATER:
442                 /* FIXME: info->size is an off_t */
443                 return info->size > (off_t) prop->value;
444         case MATCHCRITERIA_SIZE_EQUAL:
445                 /* FIXME: info->size is an off_t */
446                 return info->size == (off_t) prop->value;
447         case MATCHCRITERIA_SIZE_SMALLER:
448                 /* FIXME: info->size is an off_t */
449                 return info->size <  (off_t) prop->value;
450         case MATCHCRITERIA_NEWSGROUPS:
451                 return matcherprop_string_match(prop, info->newsgroups);
452         case MATCHCRITERIA_NOT_NEWSGROUPS:
453                 return !matcherprop_string_match(prop, info->newsgroups);
454         case MATCHCRITERIA_INREPLYTO:
455                 return matcherprop_string_match(prop, info->inreplyto);
456         case MATCHCRITERIA_NOT_INREPLYTO:
457                 return !matcherprop_string_match(prop, info->inreplyto);
458         case MATCHCRITERIA_REFERENCES:
459                 return matcherprop_string_match(prop, info->references);
460         case MATCHCRITERIA_NOT_REFERENCES:
461                 return !matcherprop_string_match(prop, info->references);
462         case MATCHCRITERIA_TEST:
463                 return matcherprop_match_test(prop, info);
464         case MATCHCRITERIA_NOT_TEST:
465                 return !matcherprop_match_test(prop, info);
466         default:
467                 return 0;
468         }
469 }
470
471 /* ********************* MatcherList *************************** */
472
473 /*!
474  *\brief        Create a new list of matchers 
475  *
476  *\param        matchers List of matcher structures
477  *\param        bool_and Operator
478  *
479  *\return       MatcherList * New list
480  */
481 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
482 {
483         MatcherList *cond;
484
485         cond = g_new0(MatcherList, 1);
486
487         cond->matchers = matchers;
488         cond->bool_and = bool_and;
489
490         return cond;
491 }
492
493 /*!
494  *\brief        Frees a list of matchers
495  *
496  *\param        cond List of matchers
497  */
498 void matcherlist_free(MatcherList *cond)
499 {
500         GSList *l;
501
502         g_return_if_fail(cond);
503         for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
504                 matcherprop_free((MatcherProp *) l->data);
505         }
506         g_free(cond);
507 }
508
509 /*!
510  *\brief        Skip all headers in a message file
511  *
512  *\param        fp Message file
513  */
514 static void matcherlist_skip_headers(FILE *fp)
515 {
516         gchar buf[BUFFSIZE];
517
518         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
519                 ;
520 }
521
522 /*!
523  *\brief        Check if a header matches a matcher condition
524  *
525  *\param        matcher Matcher structure to check header for
526  *\param        buf Header name
527  *
528  *\return       boolean TRUE if matching header
529  */
530 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
531                                              gchar *buf)
532 {
533         gboolean result;
534         Header *header;
535
536         switch (matcher->criteria) {
537         case MATCHCRITERIA_HEADER:
538         case MATCHCRITERIA_NOT_HEADER:
539                 header = procheader_parse_header(buf);
540                 if (!header)
541                         return FALSE;
542                 if (procheader_headername_equal(header->name,
543                                                 matcher->header)) {
544                         if (matcher->criteria == MATCHCRITERIA_HEADER)
545                                 result = matcherprop_string_match(matcher, header->body);
546                         else
547                                 result = !matcherprop_string_match(matcher, header->body);
548                         procheader_header_free(header);
549                         return result;
550                 }
551                 else {
552                         procheader_header_free(header);
553                 }
554                 break;
555         case MATCHCRITERIA_HEADERS_PART:
556         case MATCHCRITERIA_MESSAGE:
557                 return matcherprop_string_match(matcher, buf);
558         case MATCHCRITERIA_NOT_MESSAGE:
559         case MATCHCRITERIA_NOT_HEADERS_PART:
560                 return !matcherprop_string_match(matcher, buf);
561         }
562         return FALSE;
563 }
564
565 /*!
566  *\brief        Check if the matcher structure wants headers to
567  *              be matched
568  *
569  *\param        matcher Matcher structure
570  *
571  *\return       gboolean TRUE if the matcher structure describes
572  *              a header match condition
573  */
574 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
575 {
576         switch (matcher->criteria) {
577         case MATCHCRITERIA_HEADER:
578         case MATCHCRITERIA_NOT_HEADER:
579         case MATCHCRITERIA_HEADERS_PART:
580         case MATCHCRITERIA_NOT_HEADERS_PART:
581                 return TRUE;
582         default:
583                 return FALSE;
584         }
585 }
586
587 /*!
588  *\brief        Check if the matcher structure wants the message
589  *              to be matched (just perform an action on any
590  *              message)
591  *
592  *\param        matcher Matcher structure
593  *
594  *\return       gboolean TRUE if matcher condition should match
595  *              a message
596  */
597 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
598 {
599         switch (matcher->criteria) {
600         case MATCHCRITERIA_MESSAGE:
601         case MATCHCRITERIA_NOT_MESSAGE:
602                 return TRUE;
603         default:
604                 return FALSE;
605         }
606 }
607
608 /*!
609  *\brief        Check if a list of conditions match a header
610  *
611  *\param        matchers One set of conditions
612  *\param        buf Name of header
613  *
614  *\return       gboolean TRUE if matching should stop
615  */
616 static gboolean matcherlist_match_one_header(MatcherList *matchers,
617                                              gchar *buf)
618 {
619         GSList *l;
620
621         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
622                 MatcherProp *matcher = (MatcherProp *) l->data;
623                 
624                 /* see if a single condition matches */
625                 if (matcherprop_criteria_headers(matcher) ||
626                     matcherprop_criteria_message(matcher)) {
627                         if (matcherprop_match_one_header(matcher, buf)) {
628                                 matcher->result = TRUE;
629                         }
630                 }
631
632                 if (matcherprop_criteria_headers(matcher)) {
633                         if (matcher->result) {
634                                 if (!matchers->bool_and)
635                                         return TRUE;
636                         }
637                 }
638         }
639
640         return FALSE;
641 }
642
643 /*!
644  *\brief        Check if a list of conditions matches one header in
645  *              a message file.
646  *
647  *\param        matchers List of conditions
648  *\param        fp Message file
649  *
650  *\return       gboolean TRUE if one of the headers is matched by
651  *              the list of conditions. 
652  */
653 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
654 {
655         gchar buf[BUFFSIZE];
656
657         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
658                 if (matcherlist_match_one_header(matchers, buf))
659                         return TRUE;
660
661         return FALSE;
662 }
663
664 /*!
665  *\brief        Check if a matcher wants to check the message body
666  *
667  *\param        matcher Matcher structure
668  *
669  *\return       gboolean TRUE if body must be matched.
670  */
671 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
672 {
673         switch (matcher->criteria) {
674         case MATCHCRITERIA_BODY_PART:
675         case MATCHCRITERIA_NOT_BODY_PART:
676                 return TRUE;
677         default:
678                 return FALSE;
679         }
680 }
681
682 /*!
683  *\brief        Check if a (line) string matches the criteria
684  *              described by a matcher structure
685  *
686  *\param        matcher Matcher structure
687  *\param        line String
688  *
689  *\return       gboolean TRUE if string matches criteria
690  */
691 static gboolean matcherprop_match_line(MatcherProp *matcher, const gchar *line)
692 {
693         switch (matcher->criteria) {
694         case MATCHCRITERIA_BODY_PART:
695         case MATCHCRITERIA_MESSAGE:
696                 return matcherprop_string_match(matcher, line);
697         case MATCHCRITERIA_NOT_BODY_PART:
698         case MATCHCRITERIA_NOT_MESSAGE:
699                 return !matcherprop_string_match(matcher, line);
700         }
701         return FALSE;
702 }
703
704 /*!
705  *\brief        Check if a list of conditions matches a (line) string
706  *
707  *\param        matchers List of matchers
708  *\param        line String to match
709  *
710  *\return       gboolean TRUE if string matches list of criteria
711  */
712 static gboolean matcherlist_match_line(MatcherList *matchers, const gchar *line)
713 {
714         GSList *l;
715
716         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
717                 MatcherProp *matcher = (MatcherProp *) l->data;
718
719                 if (matcherprop_criteria_body(matcher) ||
720                     matcherprop_criteria_message(matcher)) {
721                         if (matcherprop_match_line(matcher, line)) {
722                                 matcher->result = TRUE;
723                         }
724                 }
725                         
726                 if (matcher->result) {
727                         if (!matchers->bool_and)
728                                 return TRUE;
729                 }
730         }
731         return FALSE;
732 }
733
734 /*!
735  *\brief        Check if a line in a message file's body matches
736  *              the criteria
737  *
738  *\param        matchers List of conditions
739  *\param        fp Message file
740  *
741  *\return       gboolean TRUE if succesful match
742  */
743 static gboolean matcherlist_match_body(MatcherList *matchers, FILE *fp)
744 {
745         gchar buf[BUFFSIZE];
746
747         while (fgets(buf, sizeof(buf), fp) != NULL)
748                 if (matcherlist_match_line(matchers, buf))
749                         return TRUE;
750
751         return FALSE;
752 }
753
754 /*!
755  *\brief        Check if a message file matches criteria
756  *
757  *\param        matchers Criteria
758  *\param        info Message info
759  *\param        result Default result
760  *
761  *\return       gboolean TRUE if matched
762  */
763 gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
764                                 gboolean result)
765 {
766         gboolean read_headers;
767         gboolean read_body;
768         GSList *l;
769         FILE *fp;
770         gchar *file;
771
772         /* file need to be read ? */
773
774         read_headers = FALSE;
775         read_body = FALSE;
776         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
777                 MatcherProp *matcher = (MatcherProp *) l->data;
778
779                 if (matcherprop_criteria_headers(matcher))
780                         read_headers = TRUE;
781                 if (matcherprop_criteria_body(matcher))
782                         read_body = TRUE;
783                 if (matcherprop_criteria_message(matcher)) {
784                         read_headers = TRUE;
785                         read_body = TRUE;
786                 }
787                 matcher->result = FALSE;
788         }
789
790         if (!read_headers && !read_body)
791                 return result;
792
793         file = procmsg_get_message_file(info);
794         if (file == NULL)
795                 return FALSE;
796
797         if ((fp = fopen(file, "rb")) == NULL) {
798                 FILE_OP_ERROR(file, "fopen");
799                 g_free(file);
800                 return result;
801         }
802
803         /* read the headers */
804
805         if (read_headers) {
806                 if (matcherlist_match_headers(matchers, fp))
807                         read_body = FALSE;
808         }
809         else {
810                 matcherlist_skip_headers(fp);
811         }
812
813         /* read the body */
814         if (read_body) {
815                 matcherlist_match_body(matchers, fp);
816         }
817         
818         for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
819                 MatcherProp *matcher = (MatcherProp *) l->data;
820
821                 if (matcherprop_criteria_headers(matcher) ||
822                     matcherprop_criteria_body(matcher)    ||
823                     matcherprop_criteria_message(matcher)) {
824                         if (matcher->result) {
825                                 if (!matchers->bool_and) {
826                                         result = TRUE;
827                                         break;
828                                 }
829                         }
830                         else {
831                                 if (matchers->bool_and) {
832                                         result = FALSE;
833                                         break;
834                                 }
835                         }
836                 }                       
837         }
838
839         g_free(file);
840
841         fclose(fp);
842         
843         return result;
844 }
845
846 /*!
847  *\brief        Test list of conditions on a message.
848  *
849  *\param        matchers List of conditions
850  *\param        info Message info
851  *
852  *\return       gboolean TRUE if matched
853  */
854 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
855 {
856         GSList *l;
857         gboolean result;
858
859         if (matchers->bool_and)
860                 result = TRUE;
861         else
862                 result = FALSE;
863
864         /* test the cached elements */
865
866         for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
867                 MatcherProp *matcher = (MatcherProp *) l->data;
868
869                 switch(matcher->criteria) {
870                 case MATCHCRITERIA_ALL:
871                 case MATCHCRITERIA_UNREAD:
872                 case MATCHCRITERIA_NOT_UNREAD:
873                 case MATCHCRITERIA_NEW:
874                 case MATCHCRITERIA_NOT_NEW:
875                 case MATCHCRITERIA_MARKED:
876                 case MATCHCRITERIA_NOT_MARKED:
877                 case MATCHCRITERIA_DELETED:
878                 case MATCHCRITERIA_NOT_DELETED:
879                 case MATCHCRITERIA_REPLIED:
880                 case MATCHCRITERIA_NOT_REPLIED:
881                 case MATCHCRITERIA_FORWARDED:
882                 case MATCHCRITERIA_NOT_FORWARDED:
883                 case MATCHCRITERIA_LOCKED:
884                 case MATCHCRITERIA_NOT_LOCKED:
885                 case MATCHCRITERIA_COLORLABEL:
886                 case MATCHCRITERIA_NOT_COLORLABEL:
887                 case MATCHCRITERIA_IGNORE_THREAD:
888                 case MATCHCRITERIA_NOT_IGNORE_THREAD:
889                 case MATCHCRITERIA_SUBJECT:
890                 case MATCHCRITERIA_NOT_SUBJECT:
891                 case MATCHCRITERIA_FROM:
892                 case MATCHCRITERIA_NOT_FROM:
893                 case MATCHCRITERIA_TO:
894                 case MATCHCRITERIA_NOT_TO:
895                 case MATCHCRITERIA_CC:
896                 case MATCHCRITERIA_NOT_CC:
897                 case MATCHCRITERIA_TO_OR_CC:
898                 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
899                 case MATCHCRITERIA_AGE_GREATER:
900                 case MATCHCRITERIA_AGE_LOWER:
901                 case MATCHCRITERIA_NEWSGROUPS:
902                 case MATCHCRITERIA_NOT_NEWSGROUPS:
903                 case MATCHCRITERIA_INREPLYTO:
904                 case MATCHCRITERIA_NOT_INREPLYTO:
905                 case MATCHCRITERIA_REFERENCES:
906                 case MATCHCRITERIA_NOT_REFERENCES:
907                 case MATCHCRITERIA_SCORE_GREATER:
908                 case MATCHCRITERIA_SCORE_LOWER:
909                 case MATCHCRITERIA_SCORE_EQUAL:
910                 case MATCHCRITERIA_SIZE_GREATER:
911                 case MATCHCRITERIA_SIZE_SMALLER:
912                 case MATCHCRITERIA_SIZE_EQUAL:
913                 case MATCHCRITERIA_TEST:
914                 case MATCHCRITERIA_NOT_TEST:
915                         if (matcherprop_match(matcher, info)) {
916                                 if (!matchers->bool_and) {
917                                         return TRUE;
918                                 }
919                         }
920                         else {
921                                 if (matchers->bool_and) {
922                                         return FALSE;
923                                 }
924                         }
925                 }
926         }
927
928         /* test the condition on the file */
929
930         if (matcherlist_match_file(matchers, info, result)) {
931                 if (!matchers->bool_and)
932                         return TRUE;
933         }
934         else {
935                 if (matchers->bool_and)
936                         return FALSE;
937         }
938
939         return result;
940 }
941
942
943 static gint quote_filter_str(gchar * result, guint size,
944                              const gchar * path)
945 {
946         const gchar * p;
947         gchar * result_p;
948         guint remaining;
949
950         result_p = result;
951         remaining = size;
952
953         for(p = path ; * p != '\0' ; p ++) {
954
955                 if ((* p != '\"') && (* p != '\\')) {
956                         if (remaining > 0) {
957                                 * result_p = * p;
958                                 result_p ++; 
959                                 remaining --;
960                         }
961                         else {
962                                 result[size - 1] = '\0';
963                                 return -1;
964                         }
965                 }
966                 else { 
967                         if (remaining >= 2) {
968                                 * result_p = '\\';
969                                 result_p ++; 
970                                 * result_p = * p;
971                                 result_p ++; 
972                                 remaining -= 2;
973                         }
974                         else {
975                                 result[size - 1] = '\0';
976                                 return -1;
977                         }
978                 }
979         }
980         if (remaining > 0) {
981                 * result_p = '\0';
982         }
983         else {
984                 result[size - 1] = '\0';
985                 return -1;
986         }
987   
988         return 0;
989 }
990
991
992 gchar * matcher_quote_str(const gchar * src)
993 {
994         gchar * res;
995         gint len;
996         
997         len = strlen(src) * 2 + 1;
998         res = g_malloc(len);
999         quote_filter_str(res, len, src);
1000         
1001         return res;
1002 }
1003
1004 /*!
1005  *\brief        Convert a matcher structure to a string
1006  *
1007  *\param        matcher Matcher structure
1008  *
1009  *\return       gchar * Newly allocated string
1010  */
1011 gchar *matcherprop_to_string(MatcherProp *matcher)
1012 {
1013         gchar *matcher_str = NULL;
1014         const gchar *criteria_str;
1015         const gchar *matchtype_str;
1016         int i;
1017         gchar * quoted_expr;
1018         
1019         criteria_str = NULL;
1020         for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
1021                 if (matchparser_tab[i].id == matcher->criteria)
1022                         criteria_str = matchparser_tab[i].str;
1023         }
1024         if (criteria_str == NULL)
1025                 return NULL;
1026
1027         switch (matcher->criteria) {
1028         case MATCHCRITERIA_AGE_GREATER:
1029         case MATCHCRITERIA_AGE_LOWER:
1030         case MATCHCRITERIA_SCORE_GREATER:
1031         case MATCHCRITERIA_SCORE_LOWER:
1032         case MATCHCRITERIA_SCORE_EQUAL:
1033         case MATCHCRITERIA_SIZE_GREATER:
1034         case MATCHCRITERIA_SIZE_SMALLER:
1035         case MATCHCRITERIA_SIZE_EQUAL:
1036         case MATCHCRITERIA_COLORLABEL:
1037         case MATCHCRITERIA_NOT_COLORLABEL:
1038                 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1039         case MATCHCRITERIA_ALL:
1040         case MATCHCRITERIA_UNREAD:
1041         case MATCHCRITERIA_NOT_UNREAD:
1042         case MATCHCRITERIA_NEW:
1043         case MATCHCRITERIA_NOT_NEW:
1044         case MATCHCRITERIA_MARKED:
1045         case MATCHCRITERIA_NOT_MARKED:
1046         case MATCHCRITERIA_DELETED:
1047         case MATCHCRITERIA_NOT_DELETED:
1048         case MATCHCRITERIA_REPLIED:
1049         case MATCHCRITERIA_NOT_REPLIED:
1050         case MATCHCRITERIA_FORWARDED:
1051         case MATCHCRITERIA_NOT_FORWARDED:
1052         case MATCHCRITERIA_LOCKED:
1053         case MATCHCRITERIA_NOT_LOCKED:
1054         case MATCHCRITERIA_IGNORE_THREAD:
1055         case MATCHCRITERIA_NOT_IGNORE_THREAD:
1056                 return g_strdup(criteria_str);
1057         case MATCHCRITERIA_TEST:
1058         case MATCHCRITERIA_NOT_TEST:
1059                 quoted_expr = matcher_quote_str(matcher->expr);
1060                 matcher_str = g_strdup_printf("%s \"%s\"",
1061                                               criteria_str, quoted_expr);
1062                 g_free(quoted_expr);
1063                 return matcher_str;
1064         }
1065
1066         matchtype_str = NULL;
1067         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
1068                 if (matchparser_tab[i].id == matcher->matchtype)
1069                         matchtype_str = matchparser_tab[i].str;
1070         }
1071
1072         if (matchtype_str == NULL)
1073                 return NULL;
1074
1075         switch (matcher->matchtype) {
1076         case MATCHTYPE_MATCH:
1077         case MATCHTYPE_MATCHCASE:
1078         case MATCHTYPE_REGEXP:
1079         case MATCHTYPE_REGEXPCASE:
1080                 quoted_expr = matcher_quote_str(matcher->expr);
1081                 if (matcher->header) {
1082                         gchar * quoted_header;
1083                         
1084                         quoted_header = matcher_quote_str(matcher->header);
1085                         matcher_str = g_strdup_printf
1086                                         ("%s \"%s\" %s \"%s\"",
1087                                          criteria_str, quoted_header,
1088                                          matchtype_str, quoted_expr);
1089                         g_free(quoted_header);
1090                 }
1091                 else
1092                         matcher_str = g_strdup_printf
1093                                         ("%s %s \"%s\"", criteria_str,
1094                                          matchtype_str, quoted_expr);
1095                 g_free(quoted_expr);
1096                 break;
1097         }
1098
1099         return matcher_str;
1100 }
1101
1102 /*!
1103  *\brief        Convert a list of conditions to a string
1104  *
1105  *\param        matchers List of conditions
1106  *
1107  *\return       gchar * Newly allocated string
1108  */
1109 gchar *matcherlist_to_string(const MatcherList *matchers)
1110 {
1111         gint count;
1112         gchar **vstr;
1113         GSList *l;
1114         gchar **cur_str;
1115         gchar *result = NULL;
1116
1117         count = g_slist_length(matchers->matchers);
1118         vstr = g_new(gchar *, count + 1);
1119
1120         for (l = matchers->matchers, cur_str = vstr; l != NULL;
1121              l = g_slist_next(l), cur_str ++) {
1122                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1123                 if (*cur_str == NULL)
1124                         break;
1125         }
1126         *cur_str = NULL;
1127         
1128         if (matchers->bool_and)
1129                 result = g_strjoinv(" & ", vstr);
1130         else
1131                 result = g_strjoinv(" | ", vstr);
1132
1133         for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1134                 g_free(*cur_str);
1135         g_free(vstr);
1136
1137         return result;
1138 }
1139
1140
1141 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
1142 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
1143
1144 static void add_str_default(gchar ** dest,
1145                             const gchar * s, const gchar * d)
1146 {
1147         gchar quoted_str[4096];
1148         const gchar * str;
1149         
1150         if (s != NULL)
1151                 str = s;
1152         else
1153                 str = d;
1154         
1155         quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
1156         strcpy(* dest, quoted_str);
1157         
1158         (* dest) += strlen(* dest);
1159 }
1160
1161 /* matching_build_command() - preferably cmd should be unescaped */
1162 /*!
1163  *\brief        Build the command line to execute
1164  *
1165  *\param        cmd String with command line specifiers
1166  *\param        info Message info to use for command
1167  *
1168  *\return       gchar * Newly allocated string
1169  */
1170 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
1171 {
1172         const gchar *s = cmd;
1173         gchar *filename = NULL;
1174         gchar *processed_cmd;
1175         gchar *p;
1176         gint size;
1177
1178         const gchar *const no_subject    = _("(none)") ;
1179         const gchar *const no_from       = _("(none)") ;
1180         const gchar *const no_to         = _("(none)") ;
1181         const gchar *const no_cc         = _("(none)") ;
1182         const gchar *const no_date       = _("(none)") ;
1183         const gchar *const no_msgid      = _("(none)") ;
1184         const gchar *const no_newsgroups = _("(none)") ;
1185         const gchar *const no_references = _("(none)") ;
1186
1187         size = STRLEN_ZERO(cmd) + 1;
1188         while (*s != '\0') {
1189                 if (*s == '%') {
1190                         s++;
1191                         switch (*s) {
1192                         case '%':
1193                                 size -= 1;
1194                                 break;
1195                         case 's': /* subject */
1196                                 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
1197                                 break;
1198                         case 'f': /* from */
1199                                 size += STRLEN_DEFAULT(info->from, no_from) - 2;
1200                                 break;
1201                         case 't': /* to */
1202                                 size += STRLEN_DEFAULT(info->to, no_to) - 2;
1203                                 break;
1204                         case 'c': /* cc */
1205                                 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
1206                                 break;
1207                         case 'd': /* date */
1208                                 size += STRLEN_DEFAULT(info->date, no_date) - 2;
1209                                 break;
1210                         case 'i': /* message-id */
1211                                 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
1212                                 break;
1213                         case 'n': /* newsgroups */
1214                                 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
1215                                 break;
1216                         case 'r': /* references */
1217                                 size += STRLEN_DEFAULT(info->references, no_references) - 2;
1218                                 break;
1219                         case 'F': /* file */
1220                                 if (filename == NULL)
1221                                         filename = folder_item_fetch_msg(info->folder, info->msgnum);
1222                                 
1223                                 if (filename == NULL) {
1224                                         g_warning("filename is not set");
1225                                         return NULL;
1226                                 }
1227                                 else {
1228                                         size += strlen(filename) - 2;
1229                                 }
1230                                 break;
1231                         }
1232                         s++;
1233                 }
1234                 else s++;
1235         }
1236         
1237         /* as the string can be quoted, we double the result */
1238         size *= 2;
1239
1240         processed_cmd = g_new0(gchar, size);
1241         s = cmd;
1242         p = processed_cmd;
1243
1244         while (*s != '\0') {
1245                 if (*s == '%') {
1246                         s++;
1247                         switch (*s) {
1248                         case '%':
1249                                 *p = '%';
1250                                 p++;
1251                                 break;
1252                         case 's': /* subject */
1253                                 add_str_default(&p, info->subject,
1254                                                 no_subject);
1255                                 break;
1256                         case 'f': /* from */
1257                                 add_str_default(&p, info->from,
1258                                                 no_from);
1259                                 break;
1260                         case 't': /* to */
1261                                 add_str_default(&p, info->to,
1262                                                 no_to);
1263                                 break;
1264                         case 'c': /* cc */
1265                                 add_str_default(&p, info->cc,
1266                                                 no_cc);
1267                                 break;
1268                         case 'd': /* date */
1269                                 add_str_default(&p, info->date,
1270                                                 no_date);
1271                                 break;
1272                         case 'i': /* message-id */
1273                                 add_str_default(&p, info->msgid,
1274                                                 no_msgid);
1275                                 break;
1276                         case 'n': /* newsgroups */
1277                                 add_str_default(&p, info->newsgroups,
1278                                                 no_newsgroups);
1279                                 break;
1280                         case 'r': /* references */
1281                                 add_str_default(&p, info->references,
1282                                                 no_references);
1283                                 break;
1284                         case 'F': /* file */
1285                                 if (filename != NULL)
1286                                         add_str_default(&p, filename, NULL);
1287                                 break;
1288                         default:
1289                                 *p = '%';
1290                                 p++;
1291                                 *p = *s;
1292                                 p++;
1293                                 break;
1294                         }
1295                         s++;
1296                 }
1297                 else {
1298                         *p = *s;
1299                         p++;
1300                         s++;
1301                 }
1302         }
1303         g_free(filename);
1304         
1305         return processed_cmd;
1306 }
1307 #undef STRLEN_DEFAULT
1308 #undef STRLEN_ZERO
1309
1310 /* ************************************************************ */
1311
1312
1313 /*!
1314  *\brief        Write filtering list to file
1315  *
1316  *\param        fp File
1317  *\param        prefs_filtering List of filtering conditions
1318  */
1319 static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
1320 {
1321         GSList *cur;
1322
1323         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
1324                 gchar *filtering_str;
1325                 FilteringProp *prop;
1326
1327                 if (NULL == (prop = (FilteringProp *) cur->data))
1328                         continue;
1329                 
1330                 if (NULL == (filtering_str = filteringprop_to_string(prop)))
1331                         continue;
1332                 
1333                 if (fputs(filtering_str, fp) == EOF ||
1334                     fputc('\n', fp) == EOF) {
1335                         FILE_OP_ERROR("filtering config", "fputs || fputc");
1336                         g_free(filtering_str);
1337                         return;
1338                 }
1339                 g_free(filtering_str);
1340         }
1341 }
1342
1343 /*!
1344  *\brief        Write matchers from a folder item
1345  *
1346  *\param        node Node with folder info
1347  *\param        data File pointer
1348  *
1349  *\return       gboolean FALSE
1350  */
1351 static gboolean prefs_matcher_write_func(GNode *node, gpointer data)
1352 {
1353         FolderItem *item;
1354         FILE *fp = data;
1355         gchar *id;
1356         GSList *prefs_filtering;
1357
1358         item = node->data;
1359         /* prevent warning */
1360         if (item->path == NULL)
1361                 return FALSE;
1362         id = folder_item_get_identifier(item);
1363         if (id == NULL)
1364                 return FALSE;
1365         prefs_filtering = item->prefs->processing;
1366
1367         if (prefs_filtering != NULL) {
1368                 fprintf(fp, "[%s]\n", id);
1369                 prefs_filtering_write(fp, prefs_filtering);
1370                 fputc('\n', fp);
1371         }
1372
1373         g_free(id);
1374
1375         return FALSE;
1376 }
1377
1378 /*!
1379  *\brief        Save matchers from folder items
1380  *
1381  *\param        fp File
1382  */
1383 static void prefs_matcher_save(FILE *fp)
1384 {
1385         GList *cur;
1386
1387         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1388                 Folder *folder;
1389
1390                 folder = (Folder *) cur->data;
1391                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1392                                 prefs_matcher_write_func, fp);
1393         }
1394         
1395         /* pre global rules */
1396         fprintf(fp, "[preglobal]\n");
1397         prefs_filtering_write(fp, pre_global_processing);
1398         fputc('\n', fp);
1399
1400         /* post global rules */
1401         fprintf(fp, "[postglobal]\n");
1402         prefs_filtering_write(fp, post_global_processing);
1403         fputc('\n', fp);
1404         
1405         /* filtering rules */
1406         fprintf(fp, "[filtering]\n");
1407         prefs_filtering_write(fp, filtering_rules);
1408         fputc('\n', fp);
1409 }
1410
1411 /*!
1412  *\brief        Write filtering / matcher configuration file
1413  */
1414 void prefs_matcher_write_config(void)
1415 {
1416         gchar *rcpath;
1417         PrefFile *pfile;
1418
1419         debug_print("Writing matcher configuration...\n");
1420
1421         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1422                              MATCHER_RC, NULL);
1423
1424         if ((pfile = prefs_write_open(rcpath)) == NULL) {
1425                 g_warning("failed to write configuration to file\n");
1426                 g_free(rcpath);
1427                 return;
1428         }
1429
1430
1431         prefs_matcher_save(pfile->fp);
1432
1433         g_free(rcpath);
1434
1435         if (prefs_file_close(pfile) < 0) {
1436                 g_warning("failed to write configuration to file\n");
1437                 return;
1438         }
1439 }
1440
1441 /* ******************************************************************* */
1442
1443 /*!
1444  *\brief        Read matcher configuration
1445  */
1446 void prefs_matcher_read_config(void)
1447 {
1448         gchar *rcpath;
1449         FILE *f;
1450
1451         create_matchparser_hashtab();
1452         prefs_filtering_clear();
1453
1454         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
1455         f = fopen(rcpath, "rb");
1456         g_free(rcpath);
1457
1458         if (f != NULL) {
1459                 matcher_parser_start_parsing(f);
1460                 fclose(f);
1461         }
1462         else {
1463                 /* previous version compatibily */
1464
1465                 /* printf("reading filtering\n"); */
1466                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1467                                      FILTERING_RC, NULL);
1468                 f = fopen(rcpath, "rb");
1469                 g_free(rcpath);
1470                 
1471                 if (f != NULL) {
1472                         matcher_parser_start_parsing(f);
1473                         fclose(matcher_parserin);
1474                 }
1475                 
1476                 /* printf("reading scoring\n"); */
1477                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1478                                      SCORING_RC, NULL);
1479                 f = fopen(rcpath, "rb");
1480                 g_free(rcpath);
1481                 
1482                 if (f != NULL) {
1483                         matcher_parser_start_parsing(f);
1484                         fclose(matcher_parserin);
1485                 }
1486         }
1487 }