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