sync with 0.8.11cvs36
[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         for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
619                 matcherprop_free((MatcherProp *) l->data);
620         }
621         g_free(cond);
622 }
623
624 /*!
625  *\brief        Skip all headers in a message file
626  *
627  *\param        fp Message file
628  */
629 static void matcherlist_skip_headers(FILE *fp)
630 {
631         gchar buf[BUFFSIZE];
632
633         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
634                 ;
635 }
636
637 /*!
638  *\brief        Check if a header matches a matcher condition
639  *
640  *\param        matcher Matcher structure to check header for
641  *\param        buf Header name
642  *
643  *\return       boolean TRUE if matching header
644  */
645 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
646                                              gchar *buf)
647 {
648         gboolean result;
649         Header *header;
650
651         switch (matcher->criteria) {
652         case MATCHCRITERIA_HEADER:
653         case MATCHCRITERIA_NOT_HEADER:
654                 header = procheader_parse_header(buf);
655                 if (!header)
656                         return FALSE;
657                 if (procheader_headername_equal(header->name,
658                                                 matcher->header)) {
659                         if (matcher->criteria == MATCHCRITERIA_HEADER)
660                                 result = matcherprop_string_match(matcher, header->body);
661                         else
662                                 result = !matcherprop_string_match(matcher, header->body);
663                         procheader_header_free(header);
664                         return result;
665                 }
666                 else {
667                         procheader_header_free(header);
668                 }
669                 break;
670         case MATCHCRITERIA_HEADERS_PART:
671         case MATCHCRITERIA_MESSAGE:
672                 return matcherprop_string_match(matcher, buf);
673         case MATCHCRITERIA_NOT_MESSAGE:
674         case MATCHCRITERIA_NOT_HEADERS_PART:
675                 return !matcherprop_string_match(matcher, buf);
676         }
677         return FALSE;
678 }
679
680 /*!
681  *\brief        Check if the matcher structure wants headers to
682  *              be matched
683  *
684  *\param        matcher Matcher structure
685  *
686  *\return       gboolean TRUE if the matcher structure describes
687  *              a header match condition
688  */
689 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
690 {
691         switch (matcher->criteria) {
692         case MATCHCRITERIA_HEADER:
693         case MATCHCRITERIA_NOT_HEADER:
694         case MATCHCRITERIA_HEADERS_PART:
695         case MATCHCRITERIA_NOT_HEADERS_PART:
696                 return TRUE;
697         default:
698                 return FALSE;
699         }
700 }
701
702 /*!
703  *\brief        Check if the matcher structure wants the message
704  *              to be matched (just perform an action on any
705  *              message)
706  *
707  *\param        matcher Matcher structure
708  *
709  *\return       gboolean TRUE if matcher condition should match
710  *              a message
711  */
712 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
713 {
714         switch (matcher->criteria) {
715         case MATCHCRITERIA_MESSAGE:
716         case MATCHCRITERIA_NOT_MESSAGE:
717                 return TRUE;
718         default:
719                 return FALSE;
720         }
721 }
722
723 /*!
724  *\brief        Check if a list of conditions match a header
725  *
726  *\param        matchers One set of conditions
727  *\param        buf Name of header
728  *
729  *\return       gboolean TRUE if matching should stop
730  */
731 static gboolean matcherlist_match_one_header(MatcherList *matchers,
732                                              gchar *buf)
733 {
734         GSList *l;
735
736         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
737                 MatcherProp *matcher = (MatcherProp *) l->data;
738                 
739                 /* see if a single condition matches */
740                 if (matcherprop_criteria_headers(matcher) ||
741                     matcherprop_criteria_message(matcher)) {
742                         if (matcherprop_match_one_header(matcher, buf)) {
743                                 matcher->result = TRUE;
744                         }
745                 }
746
747                 if (matcherprop_criteria_headers(matcher)) {
748                         if (matcher->result) {
749                                 if (!matchers->bool_and)
750                                         return TRUE;
751                         }
752                 }
753         }
754
755         return FALSE;
756 }
757
758 /*!
759  *\brief        Check if a list of conditions matches one header in
760  *              a message file.
761  *
762  *\param        matchers List of conditions
763  *\param        fp Message file
764  *
765  *\return       gboolean TRUE if one of the headers is matched by
766  *              the list of conditions. 
767  */
768 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
769 {
770         gchar buf[BUFFSIZE];
771
772         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
773                 if (matcherlist_match_one_header(matchers, buf))
774                         return TRUE;
775
776         return FALSE;
777 }
778
779 /*!
780  *\brief        Check if a matcher wants to check the message body
781  *
782  *\param        matcher Matcher structure
783  *
784  *\return       gboolean TRUE if body must be matched.
785  */
786 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
787 {
788         switch (matcher->criteria) {
789         case MATCHCRITERIA_BODY_PART:
790         case MATCHCRITERIA_NOT_BODY_PART:
791                 return TRUE;
792         default:
793                 return FALSE;
794         }
795 }
796
797 /*!
798  *\brief        Check if a (line) string matches the criteria
799  *              described by a matcher structure
800  *
801  *\param        matcher Matcher structure
802  *\param        line String
803  *
804  *\return       gboolean TRUE if string matches criteria
805  */
806 static gboolean matcherprop_match_line(MatcherProp *matcher, const gchar *line)
807 {
808         switch (matcher->criteria) {
809         case MATCHCRITERIA_BODY_PART:
810         case MATCHCRITERIA_MESSAGE:
811                 return matcherprop_string_match(matcher, line);
812         case MATCHCRITERIA_NOT_BODY_PART:
813         case MATCHCRITERIA_NOT_MESSAGE:
814                 return !matcherprop_string_match(matcher, line);
815         }
816         return FALSE;
817 }
818
819 /*!
820  *\brief        Check if a list of conditions matches a (line) string
821  *
822  *\param        matchers List of matchers
823  *\param        line String to match
824  *
825  *\return       gboolean TRUE if string matches list of criteria
826  */
827 static gboolean matcherlist_match_line(MatcherList *matchers, const gchar *line)
828 {
829         GSList *l;
830
831         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
832                 MatcherProp *matcher = (MatcherProp *) l->data;
833
834                 if (matcherprop_criteria_body(matcher) ||
835                     matcherprop_criteria_message(matcher)) {
836                         if (matcherprop_match_line(matcher, line)) {
837                                 matcher->result = TRUE;
838                         }
839                 }
840                         
841                 if (matcher->result) {
842                         if (!matchers->bool_and)
843                                 return TRUE;
844                 }
845         }
846         return FALSE;
847 }
848
849 /*!
850  *\brief        Check if a line in a message file's body matches
851  *              the criteria
852  *
853  *\param        matchers List of conditions
854  *\param        fp Message file
855  *
856  *\return       gboolean TRUE if succesful match
857  */
858 static gboolean matcherlist_match_body(MatcherList *matchers, FILE *fp)
859 {
860         gchar buf[BUFFSIZE];
861
862         while (fgets(buf, sizeof(buf), fp) != NULL)
863                 if (matcherlist_match_line(matchers, buf))
864                         return TRUE;
865
866         return FALSE;
867 }
868
869 /*!
870  *\brief        Check if a message file matches criteria
871  *
872  *\param        matchers Criteria
873  *\param        info Message info
874  *\param        result Default result
875  *
876  *\return       gboolean TRUE if matched
877  */
878 gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
879                                 gboolean result)
880 {
881         gboolean read_headers;
882         gboolean read_body;
883         GSList *l;
884         FILE *fp;
885         gchar *file;
886
887         /* file need to be read ? */
888
889         read_headers = FALSE;
890         read_body = FALSE;
891         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
892                 MatcherProp *matcher = (MatcherProp *) l->data;
893
894                 if (matcherprop_criteria_headers(matcher))
895                         read_headers = TRUE;
896                 if (matcherprop_criteria_body(matcher))
897                         read_body = TRUE;
898                 if (matcherprop_criteria_message(matcher)) {
899                         read_headers = TRUE;
900                         read_body = TRUE;
901                 }
902                 matcher->result = FALSE;
903         }
904
905         if (!read_headers && !read_body)
906                 return result;
907
908         file = procmsg_get_message_file(info);
909         if (file == NULL)
910                 return FALSE;
911
912         if ((fp = fopen(file, "rb")) == NULL) {
913                 FILE_OP_ERROR(file, "fopen");
914                 g_free(file);
915                 return result;
916         }
917
918         /* read the headers */
919
920         if (read_headers) {
921                 if (matcherlist_match_headers(matchers, fp))
922                         read_body = FALSE;
923         }
924         else {
925                 matcherlist_skip_headers(fp);
926         }
927
928         /* read the body */
929         if (read_body) {
930                 matcherlist_match_body(matchers, fp);
931         }
932         
933         for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
934                 MatcherProp *matcher = (MatcherProp *) l->data;
935
936                 if (matcherprop_criteria_headers(matcher) ||
937                     matcherprop_criteria_body(matcher)    ||
938                     matcherprop_criteria_message(matcher)) {
939                         if (matcher->result) {
940                                 if (!matchers->bool_and) {
941                                         result = TRUE;
942                                         break;
943                                 }
944                         }
945                         else {
946                                 if (matchers->bool_and) {
947                                         result = FALSE;
948                                         break;
949                                 }
950                         }
951                 }                       
952         }
953
954         g_free(file);
955
956         fclose(fp);
957         
958         return result;
959 }
960
961 /*!
962  *\brief        Test list of conditions on a message.
963  *
964  *\param        matchers List of conditions
965  *\param        info Message info
966  *
967  *\return       gboolean TRUE if matched
968  */
969 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
970 {
971         GSList *l;
972         gboolean result;
973
974         if (matchers->bool_and)
975                 result = TRUE;
976         else
977                 result = FALSE;
978
979         /* test the cached elements */
980
981         for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
982                 MatcherProp *matcher = (MatcherProp *) l->data;
983
984                 switch(matcher->criteria) {
985                 case MATCHCRITERIA_ALL:
986                 case MATCHCRITERIA_UNREAD:
987                 case MATCHCRITERIA_NOT_UNREAD:
988                 case MATCHCRITERIA_NEW:
989                 case MATCHCRITERIA_NOT_NEW:
990                 case MATCHCRITERIA_MARKED:
991                 case MATCHCRITERIA_NOT_MARKED:
992                 case MATCHCRITERIA_DELETED:
993                 case MATCHCRITERIA_NOT_DELETED:
994                 case MATCHCRITERIA_REPLIED:
995                 case MATCHCRITERIA_NOT_REPLIED:
996                 case MATCHCRITERIA_FORWARDED:
997                 case MATCHCRITERIA_NOT_FORWARDED:
998                 case MATCHCRITERIA_LOCKED:
999                 case MATCHCRITERIA_NOT_LOCKED:
1000                 case MATCHCRITERIA_COLORLABEL:
1001                 case MATCHCRITERIA_NOT_COLORLABEL:
1002                 case MATCHCRITERIA_IGNORE_THREAD:
1003                 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1004                 case MATCHCRITERIA_SUBJECT:
1005                 case MATCHCRITERIA_NOT_SUBJECT:
1006                 case MATCHCRITERIA_FROM:
1007                 case MATCHCRITERIA_NOT_FROM:
1008                 case MATCHCRITERIA_TO:
1009                 case MATCHCRITERIA_NOT_TO:
1010                 case MATCHCRITERIA_CC:
1011                 case MATCHCRITERIA_NOT_CC:
1012                 case MATCHCRITERIA_TO_OR_CC:
1013                 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1014                 case MATCHCRITERIA_AGE_GREATER:
1015                 case MATCHCRITERIA_AGE_LOWER:
1016                 case MATCHCRITERIA_NEWSGROUPS:
1017                 case MATCHCRITERIA_NOT_NEWSGROUPS:
1018                 case MATCHCRITERIA_INREPLYTO:
1019                 case MATCHCRITERIA_NOT_INREPLYTO:
1020                 case MATCHCRITERIA_REFERENCES:
1021                 case MATCHCRITERIA_NOT_REFERENCES:
1022                 case MATCHCRITERIA_SCORE_GREATER:
1023                 case MATCHCRITERIA_SCORE_LOWER:
1024                 case MATCHCRITERIA_SCORE_EQUAL:
1025                 case MATCHCRITERIA_SIZE_GREATER:
1026                 case MATCHCRITERIA_SIZE_SMALLER:
1027                 case MATCHCRITERIA_SIZE_EQUAL:
1028                 case MATCHCRITERIA_EXECUTE:
1029                 case MATCHCRITERIA_NOT_EXECUTE:
1030                         if (matcherprop_match(matcher, info)) {
1031                                 if (!matchers->bool_and) {
1032                                         return TRUE;
1033                                 }
1034                         }
1035                         else {
1036                                 if (matchers->bool_and) {
1037                                         return FALSE;
1038                                 }
1039                         }
1040                 }
1041         }
1042
1043         /* test the condition on the file */
1044
1045         if (matcherlist_match_file(matchers, info, result)) {
1046                 if (!matchers->bool_and)
1047                         return TRUE;
1048         }
1049         else {
1050                 if (matchers->bool_and)
1051                         return FALSE;
1052         }
1053
1054         return result;
1055 }
1056
1057 /*!
1058  *\brief        Convert a matcher structure to a string
1059  *
1060  *\param        matcher Matcher structure
1061  *
1062  *\return       gchar * Newly allocated string
1063  */
1064 gchar *matcherprop_to_string(MatcherProp *matcher)
1065 {
1066         gchar *matcher_str = NULL;
1067         const gchar *criteria_str;
1068         const gchar *matchtype_str;
1069         int i;
1070         gchar *expr;
1071
1072         criteria_str = NULL;
1073         for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
1074                 if (matchparser_tab[i].id == matcher->criteria)
1075                         criteria_str = matchparser_tab[i].str;
1076         }
1077         if (criteria_str == NULL)
1078                 return NULL;
1079
1080         switch (matcher->criteria) {
1081         case MATCHCRITERIA_AGE_GREATER:
1082         case MATCHCRITERIA_AGE_LOWER:
1083         case MATCHCRITERIA_SCORE_GREATER:
1084         case MATCHCRITERIA_SCORE_LOWER:
1085         case MATCHCRITERIA_SCORE_EQUAL:
1086         case MATCHCRITERIA_SIZE_GREATER:
1087         case MATCHCRITERIA_SIZE_SMALLER:
1088         case MATCHCRITERIA_SIZE_EQUAL:
1089         case MATCHCRITERIA_COLORLABEL:
1090         case MATCHCRITERIA_NOT_COLORLABEL:
1091                 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1092         case MATCHCRITERIA_ALL:
1093         case MATCHCRITERIA_UNREAD:
1094         case MATCHCRITERIA_NOT_UNREAD:
1095         case MATCHCRITERIA_NEW:
1096         case MATCHCRITERIA_NOT_NEW:
1097         case MATCHCRITERIA_MARKED:
1098         case MATCHCRITERIA_NOT_MARKED:
1099         case MATCHCRITERIA_DELETED:
1100         case MATCHCRITERIA_NOT_DELETED:
1101         case MATCHCRITERIA_REPLIED:
1102         case MATCHCRITERIA_NOT_REPLIED:
1103         case MATCHCRITERIA_FORWARDED:
1104         case MATCHCRITERIA_NOT_FORWARDED:
1105         case MATCHCRITERIA_LOCKED:
1106         case MATCHCRITERIA_NOT_LOCKED:
1107         case MATCHCRITERIA_IGNORE_THREAD:
1108         case MATCHCRITERIA_NOT_IGNORE_THREAD:
1109                 return g_strdup(criteria_str);
1110         case MATCHCRITERIA_EXECUTE:
1111         case MATCHCRITERIA_NOT_EXECUTE:
1112                 expr = matcher_escape_str(matcher->expr);
1113                 matcher_str = g_strdup_printf("%s \"%s\"", criteria_str, expr);
1114                 g_free((gpointer) expr);
1115                 return matcher_str;
1116         }
1117
1118         matchtype_str = NULL;
1119         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
1120                 if (matchparser_tab[i].id == matcher->matchtype)
1121                         matchtype_str = matchparser_tab[i].str;
1122         }
1123
1124         if (matchtype_str == NULL)
1125                 return NULL;
1126
1127         switch (matcher->matchtype) {
1128         case MATCHTYPE_MATCH:
1129         case MATCHTYPE_MATCHCASE:
1130         case MATCHTYPE_REGEXP:
1131         case MATCHTYPE_REGEXPCASE:
1132                 expr = matcher_escape_str(matcher->expr);
1133                 if (matcher->header)
1134                         matcher_str = g_strdup_printf
1135                                         ("%s \"%s\" %s \"%s\"",
1136                                          criteria_str, matcher->header,
1137                                          matchtype_str, expr);
1138                 else
1139                         matcher_str = g_strdup_printf
1140                                         ("%s %s \"%s\"", criteria_str,
1141                                          matchtype_str, expr);
1142                 g_free((gpointer) expr);
1143                 break;
1144         }
1145
1146         return matcher_str;
1147 }
1148
1149 /*!
1150  *\brief        Convert a list of conditions to a string
1151  *
1152  *\param        matchers List of conditions
1153  *
1154  *\return       gchar * Newly allocated string
1155  */
1156 gchar *matcherlist_to_string(const MatcherList *matchers)
1157 {
1158         gint count;
1159         gchar **vstr;
1160         GSList *l;
1161         gchar **cur_str;
1162         gchar *result = NULL;
1163
1164         count = g_slist_length(matchers->matchers);
1165         vstr = g_new(gchar *, count + 1);
1166
1167         for (l = matchers->matchers, cur_str = vstr; l != NULL;
1168              l = g_slist_next(l), cur_str ++) {
1169                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1170                 if (*cur_str == NULL)
1171                         break;
1172         }
1173         *cur_str = NULL;
1174         
1175         if (matchers->bool_and)
1176                 result = g_strjoinv(" & ", vstr);
1177         else
1178                 result = g_strjoinv(" | ", vstr);
1179
1180         for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1181                 g_free(*cur_str);
1182         g_free(vstr);
1183
1184         return result;
1185 }
1186
1187 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
1188 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
1189 /* matching_build_command() - preferably cmd should be unescaped */
1190 /*!
1191  *\brief        Build the command line to execute
1192  *
1193  *\param        cmd String with command line specifiers
1194  *\param        info Message info to use for command
1195  *
1196  *\return       gchar * Newly allocated string
1197  *
1198  *\warning      The \a cmd string should have been unescaped using
1199  *              #matcher_unescape_str.
1200  */
1201 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
1202 {
1203         const gchar *s = cmd;
1204         gchar *filename = NULL;
1205         gchar *processed_cmd;
1206         gchar *p;
1207         gint size;
1208
1209         const gchar *const no_subject    = _("(none)") ;
1210         const gchar *const no_from       = _("(none)") ;
1211         const gchar *const no_to         = _("(none)") ;
1212         const gchar *const no_cc         = _("(none)") ;
1213         const gchar *const no_date       = _("(none)") ;
1214         const gchar *const no_msgid      = _("(none)") ;
1215         const gchar *const no_newsgroups = _("(none)") ;
1216         const gchar *const no_references = _("(none)") ;
1217
1218         size = STRLEN_ZERO(cmd) + 1;
1219         while (*s != '\0') {
1220                 if (*s == '%') {
1221                         s++;
1222                         switch (*s) {
1223                         case '%':
1224                                 size -= 1;
1225                                 break;
1226                         case 's': /* subject */
1227                                 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
1228                                 break;
1229                         case 'f': /* from */
1230                                 size += STRLEN_DEFAULT(info->from, no_from) - 2;
1231                                 break;
1232                         case 't': /* to */
1233                                 size += STRLEN_DEFAULT(info->to, no_to) - 2;
1234                                 break;
1235                         case 'c': /* cc */
1236                                 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
1237                                 break;
1238                         case 'd': /* date */
1239                                 size += STRLEN_DEFAULT(info->date, no_date) - 2;
1240                                 break;
1241                         case 'i': /* message-id */
1242                                 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
1243                                 break;
1244                         case 'n': /* newsgroups */
1245                                 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
1246                                 break;
1247                         case 'r': /* references */
1248                                 size += STRLEN_DEFAULT(info->references, no_references) - 2;
1249                                 break;
1250                         case 'F': /* file */
1251                                 filename = folder_item_fetch_msg(info->folder,
1252                                                                  info->msgnum);
1253                                 
1254                                 if (filename == NULL) {
1255                                         g_warning("filename is not set");
1256                                         return NULL;
1257                                 }
1258                                 else
1259                                         size += strlen(filename) - 2;
1260                                 break;
1261                         }
1262                         s++;
1263                 }
1264                 else s++;
1265         }
1266
1267         processed_cmd = g_new0(gchar, size);
1268         s = cmd;
1269         p = processed_cmd;
1270
1271         while (*s != '\0') {
1272                 if (*s == '%') {
1273                         s++;
1274                         switch (*s) {
1275                         case '%':
1276                                 *p = '%';
1277                                 p++;
1278                                 break;
1279                         case 's': /* subject */
1280                                 if (info->subject != NULL)
1281                                         strcpy(p, info->subject);
1282                                 else
1283                                         strcpy(p, no_subject);
1284                                 p += strlen(p);
1285                                 break;
1286                         case 'f': /* from */
1287                                 if (info->from != NULL)
1288                                         strcpy(p, info->from);
1289                                 else
1290                                         strcpy(p, no_from);
1291                                 p += strlen(p);
1292                                 break;
1293                         case 't': /* to */
1294                                 if (info->to != NULL)
1295                                         strcpy(p, info->to);
1296                                 else
1297                                         strcpy(p, no_to);
1298                                 p += strlen(p);
1299                                 break;
1300                         case 'c': /* cc */
1301                                 if (info->cc != NULL)
1302                                         strcpy(p, info->cc);
1303                                 else
1304                                         strcpy(p, no_cc);
1305                                 p += strlen(p);
1306                                 break;
1307                         case 'd': /* date */
1308                                 if (info->date != NULL)
1309                                         strcpy(p, info->date);
1310                                 else
1311                                         strcpy(p, no_date);
1312                                 p += strlen(p);
1313                                 break;
1314                         case 'i': /* message-id */
1315                                 if (info->msgid != NULL)
1316                                         strcpy(p, info->msgid);
1317                                 else
1318                                         strcpy(p, no_msgid);
1319                                 p += strlen(p);
1320                                 break;
1321                         case 'n': /* newsgroups */
1322                                 if (info->newsgroups != NULL)
1323                                         strcpy(p, info->newsgroups);
1324                                 else
1325                                         strcpy(p, no_newsgroups);
1326                                 p += strlen(p);
1327                                 break;
1328                         case 'r': /* references */
1329                                 if (info->references != NULL)
1330                                         strcpy(p, info->references);
1331                                 else
1332                                         strcpy(p, no_references);
1333                                 p += strlen(p);
1334                                 break;
1335                         case 'F': /* file */
1336                                 strcpy(p, filename);
1337                                 p += strlen(p);
1338                                 g_free(filename);
1339                                 break;
1340                         default:
1341                                 *p = '%';
1342                                 p++;
1343                                 *p = *s;
1344                                 p++;
1345                                 break;
1346                         }
1347                         s++;
1348                 }
1349                 else {
1350                         *p = *s;
1351                         p++;
1352                         s++;
1353                 }
1354         }
1355
1356         return processed_cmd;
1357 }
1358 #undef STRLEN_DEFAULT
1359 #undef STRLEN_ZERO
1360
1361 /* ************************************************************ */
1362
1363 /*!
1364  *\brief        Write scoring list to file
1365  *
1366  *\param        fp File
1367  *\param        prefs_scoring List of scoring conditions
1368  */
1369 static void prefs_scoring_write(FILE *fp, GSList *prefs_scoring)
1370 {
1371         GSList *cur;
1372
1373         for (cur = prefs_scoring; cur != NULL; cur = cur->next) {
1374                 gchar *scoring_str;
1375                 ScoringProp *prop;
1376
1377                 prop = (ScoringProp *) cur->data;
1378                 scoring_str = scoringprop_to_string(prop);
1379                 if (fputs(scoring_str, fp) == EOF ||
1380                     fputc('\n', fp) == EOF) {
1381                         FILE_OP_ERROR("scoring config", "fputs || fputc");
1382                         g_free(scoring_str);
1383                         return;
1384                 }
1385                 g_free(scoring_str);
1386         }
1387 }
1388
1389 /*!
1390  *\brief        Write filtering list to file
1391  *
1392  *\param        fp File
1393  *\param        prefs_filtering List of filtering conditions
1394  */
1395 static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
1396 {
1397         GSList *cur;
1398
1399         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
1400                 gchar *filtering_str;
1401                 FilteringProp *prop;
1402
1403                 if (NULL == (prop = (FilteringProp *) cur->data))
1404                         continue;
1405                 
1406                 if (NULL == (filtering_str = filteringprop_to_string(prop)))
1407                         continue;
1408                 
1409                 if (fputs(filtering_str, fp) == EOF ||
1410                     fputc('\n', fp) == EOF) {
1411                         FILE_OP_ERROR("filtering config", "fputs || fputc");
1412                         g_free(filtering_str);
1413                         return;
1414                 }
1415                 g_free(filtering_str);
1416         }
1417 }
1418
1419 /*!
1420  *\brief        Write matchers from a folder item
1421  *
1422  *\param        node Node with folder info
1423  *\param        data File pointer
1424  *
1425  *\return       gboolean FALSE
1426  */
1427 static gboolean prefs_matcher_write_func(GNode *node, gpointer data)
1428 {
1429         FolderItem *item;
1430         FILE *fp = data;
1431         gchar *id;
1432         GSList *prefs_scoring;
1433         GSList *prefs_filtering;
1434
1435         if (node != NULL) {
1436                 item = node->data;
1437                 /* prevent warning */
1438                 if (item->path == NULL)
1439                         return FALSE;
1440                 id = folder_item_get_identifier(item);
1441                 if (id == NULL)
1442                         return FALSE;
1443                 prefs_scoring = item->prefs->scoring;
1444                 prefs_filtering = item->prefs->processing;
1445         }
1446         else {
1447                 item = NULL;
1448                 id = g_strdup("global"); /* because it is g_freed */
1449                 prefs_scoring = global_scoring;
1450                 prefs_filtering = global_processing;
1451         }
1452
1453         if (prefs_filtering != NULL || prefs_scoring != NULL) {
1454                 fprintf(fp, "[%s]\n", id);
1455
1456                 prefs_filtering_write(fp, prefs_filtering);
1457                 prefs_scoring_write(fp, prefs_scoring);
1458
1459                 fputc('\n', fp);
1460         }
1461
1462         g_free(id);
1463
1464         return FALSE;
1465 }
1466
1467 /*!
1468  *\brief        Save matchers from folder items
1469  *
1470  *\param        fp File
1471  */
1472 static void prefs_matcher_save(FILE *fp)
1473 {
1474         GList *cur;
1475
1476         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1477                 Folder *folder;
1478
1479                 folder = (Folder *) cur->data;
1480                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1481                                 prefs_matcher_write_func, fp);
1482         }
1483         prefs_matcher_write_func(NULL, fp);
1484 }
1485
1486 /*!
1487  *\brief        Write filtering / matcher configuration file
1488  */
1489 void prefs_matcher_write_config(void)
1490 {
1491         gchar *rcpath;
1492         PrefFile *pfile;
1493
1494         debug_print("Writing matcher configuration...\n");
1495
1496         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1497                              MATCHER_RC, NULL);
1498
1499         if ((pfile = prefs_write_open(rcpath)) == NULL) {
1500                 g_warning("failed to write configuration to file\n");
1501                 g_free(rcpath);
1502                 return;
1503         }
1504
1505
1506         prefs_matcher_save(pfile->fp);
1507
1508         g_free(rcpath);
1509
1510         if (prefs_file_close(pfile) < 0) {
1511                 g_warning("failed to write configuration to file\n");
1512                 return;
1513         }
1514 }
1515
1516 /* ******************************************************************* */
1517
1518 /*!
1519  *\brief        Read matcher configuration
1520  */
1521 void prefs_matcher_read_config(void)
1522 {
1523         gchar *rcpath;
1524         FILE *f;
1525
1526         create_matchparser_hashtab();
1527         prefs_scoring_clear();
1528         prefs_filtering_clear();
1529
1530         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
1531         f = fopen(rcpath, "rb");
1532         g_free(rcpath);
1533
1534         if (f != NULL) {
1535                 matcher_parser_start_parsing(f);
1536                 fclose(f);
1537         }
1538         else {
1539                 /* previous version compatibily */
1540
1541                 /* printf("reading filtering\n"); */
1542                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1543                                      FILTERING_RC, NULL);
1544                 f = fopen(rcpath, "rb");
1545                 g_free(rcpath);
1546                 
1547                 if (f != NULL) {
1548                         matcher_parser_start_parsing(f);
1549                         fclose(matcher_parserin);
1550                 }
1551                 
1552                 /* printf("reading scoring\n"); */
1553                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1554                                      SCORING_RC, NULL);
1555                 f = fopen(rcpath, "rb");
1556                 g_free(rcpath);
1557                 
1558                 if (f != NULL) {
1559                         matcher_parser_start_parsing(f);
1560                         fclose(matcher_parserin);
1561                 }
1562         }
1563 }