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