0.8.11claws39
[claws.git] / src / matcher.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002 by the Sylpheed Claws Team and Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include <ctype.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <errno.h>
24 #include "defs.h"
25 #include "utils.h"
26 #include "procheader.h"
27 #include "matcher.h"
28 #include "intl.h"
29 #include "matcher_parser.h"
30 #include "prefs_gtk.h"
31
32 /*!
33  *\brief        Keyword lookup element
34  */
35 struct _MatchParser {
36         gint id;                /*!< keyword id */ 
37         gchar *str;             /*!< keyword */
38 };
39 typedef struct _MatchParser MatchParser;
40
41 /*!
42  *\brief        Table with strings and ids used by the lexer and
43  *              the parser. New keywords can be added here.
44  */
45 static const MatchParser matchparser_tab[] = {
46         /* msginfo flags */
47         {MATCHCRITERIA_ALL, "all"},
48         {MATCHCRITERIA_UNREAD, "unread"},
49         {MATCHCRITERIA_NOT_UNREAD, "~unread"},
50         {MATCHCRITERIA_NEW, "new"},
51         {MATCHCRITERIA_NOT_NEW, "~new"},
52         {MATCHCRITERIA_MARKED, "marked"},
53         {MATCHCRITERIA_NOT_MARKED, "~marked"},
54         {MATCHCRITERIA_DELETED, "deleted"},
55         {MATCHCRITERIA_NOT_DELETED, "~deleted"},
56         {MATCHCRITERIA_REPLIED, "replied"},
57         {MATCHCRITERIA_NOT_REPLIED, "~replied"},
58         {MATCHCRITERIA_FORWARDED, "forwarded"},
59         {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
60         {MATCHCRITERIA_LOCKED, "locked"},
61         {MATCHCRITERIA_NOT_LOCKED, "~locked"},
62         {MATCHCRITERIA_COLORLABEL, "colorlabel"},
63         {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
64         {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
65         {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
66
67         /* msginfo headers */
68         {MATCHCRITERIA_SUBJECT, "subject"},
69         {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
70         {MATCHCRITERIA_FROM, "from"},
71         {MATCHCRITERIA_NOT_FROM, "~from"},
72         {MATCHCRITERIA_TO, "to"},
73         {MATCHCRITERIA_NOT_TO, "~to"},
74         {MATCHCRITERIA_CC, "cc"},
75         {MATCHCRITERIA_NOT_CC, "~cc"},
76         {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
77         {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
78         {MATCHCRITERIA_AGE_GREATER, "age_greater"},
79         {MATCHCRITERIA_AGE_LOWER, "age_lower"},
80         {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
81         {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
82         {MATCHCRITERIA_INREPLYTO, "inreplyto"},
83         {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
84         {MATCHCRITERIA_REFERENCES, "references"},
85         {MATCHCRITERIA_NOT_REFERENCES, "~references"},
86         {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
87         {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
88         {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
89
90         {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
91         {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
92         {MATCHCRITERIA_SIZE_EQUAL,   "size_equal"},
93
94         /* content have to be read */
95         {MATCHCRITERIA_HEADER, "header"},
96         {MATCHCRITERIA_NOT_HEADER, "~header"},
97         {MATCHCRITERIA_HEADERS_PART, "headers_part"},
98         {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
99         {MATCHCRITERIA_MESSAGE, "message"},
100         {MATCHCRITERIA_NOT_MESSAGE, "~message"},
101         {MATCHCRITERIA_BODY_PART, "body_part"},
102         {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
103         {MATCHCRITERIA_EXECUTE, "execute"},
104         {MATCHCRITERIA_NOT_EXECUTE, "~execute"},
105
106         /* match type */
107         {MATCHTYPE_MATCHCASE, "matchcase"},
108         {MATCHTYPE_MATCH, "match"},
109         {MATCHTYPE_REGEXPCASE, "regexpcase"},
110         {MATCHTYPE_REGEXP, "regexp"},
111
112         /* actions */
113         {MATCHACTION_SCORE, "score"},
114         {MATCHACTION_MOVE, "move"},
115         {MATCHACTION_COPY, "copy"},
116         {MATCHACTION_DELETE, "delete"},
117         {MATCHACTION_MARK, "mark"},
118         {MATCHACTION_UNMARK, "unmark"},
119         {MATCHACTION_MARK_AS_READ, "mark_as_read"},
120         {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
121         {MATCHACTION_FORWARD, "forward"},
122         {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
123         {MATCHACTION_EXECUTE, "execute"},
124         {MATCHACTION_COLOR, "color"},
125         {MATCHACTION_REDIRECT, "redirect"},
126         {MATCHACTION_DELETE_ON_SERVER, "delete_on_server"},
127         {MATCHACTION_CHANGE_SCORE, "change_score"}
128 };
129
130 /*!
131  *\brief        Look up table with keywords defined in \sa matchparser_tab
132  */
133 static GHashTable *matchparser_hashtab;
134
135 /*!
136  *\brief        Translate keyword id to keyword string
137  *
138  *\param        id Id of keyword
139  *
140  *\return       const gchar * Keyword
141  */
142 const gchar *get_matchparser_tab_str(gint id)
143 {
144         gint i;
145
146         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
147                 if (matchparser_tab[i].id == id)
148                         return matchparser_tab[i].str;
149         }
150         return NULL;
151 }
152
153 /*!
154  *\brief        Create keyword lookup table
155  */
156 static void create_matchparser_hashtab(void)
157 {
158         int i;
159         
160         if (matchparser_hashtab) return;
161         matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
162         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
163                 g_hash_table_insert(matchparser_hashtab,
164                                     matchparser_tab[i].str,
165                                     (gpointer) &matchparser_tab[i]);
166 }
167
168 /*!
169  *\brief        Return a keyword id from a keyword string
170  *
171  *\param        str Keyword string
172  *
173  *\return       gint Keyword id
174  */
175 gint get_matchparser_tab_id(const gchar *str)
176 {
177         MatchParser *res;
178
179         if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
180                 return res->id;
181         } else
182                 return -1;
183 }
184
185 /*!
186  *\brief        Escape characters in a string by inserting '\' characters
187  *
188  *\param        str String with characters to be escaped
189  *
190  *\return       gchar * Newly allocated string with escaped characters
191  */
192 gchar *matcher_escape_str(const gchar *str)
193 {
194         register const gchar *walk;
195         register int escape;
196         gchar *res;
197         register char *reswalk;
198
199         if (str == NULL)
200                 return NULL;
201
202         for (escape = 0, walk = str; *walk; walk++)
203                 if (*walk == '\\' || *walk == '\'' || *walk == '\"')
204                         escape++;
205
206         if (!escape)
207                 return g_strdup(str);
208         
209         reswalk = res = g_new0(gchar, (walk - str) + escape + 1);
210         for (walk = str; *walk; walk++, reswalk++) {
211                 if (*walk == '\\' || *walk == '\'' || *walk == '\"')
212                         *reswalk++ = '\\';
213                 *reswalk = *walk;
214         }
215
216         *reswalk = 0;
217         return res;
218 }
219
220 /*!
221  *\brief        Unescape string by replacing escaped char sequences
222  *              (\b, \n, etc) by their actual char. Note that this
223  *              function changes the contents of the buffer pointed
224  *              to by \a str.
225  *
226  *\param        str Buffer containing string that needs to be escaped.
227  *              Note that this function changes the contents of the
228  *              buffer
229  *
230  *\return       gchar * Pointer to changed buffer
231  */
232 gchar *matcher_unescape_str(gchar *str)
233 {
234         gchar *tmp = alloca(strlen(str) + 1);
235         register gchar *src = tmp;
236         register gchar *dst = str;
237         
238         strcpy(tmp, str);
239
240         for ( ; *src; src++) {
241                 if (*src != '\\') 
242                         *dst++ = *src;
243                 else {
244                         src++;
245                         if (*src == 'n')   /* insert control characters */
246                                 *dst++ = '\n';
247                         else if (*src == 'r') 
248                                 *dst++ = '\r';
249                         else if (*src == 't') 
250                                 *dst++ = '\t';
251                         else if (*src == 'r') 
252                                 *dst++ = '\r';
253                         else if (*src == 'b')
254                                 *dst++ = '\b';
255                         else if (*src == 'f')
256                                 *dst++ = '\f';
257                         else if (*src == '\\' || *src == '\'' || *src == '\"')
258                                 /* insert \\, \' or \" */
259                                 *dst++ = *src;
260                         else {
261                                 /* FIXME: should perhaps escape character... */
262                                 src--;
263                                 *dst++ = *src;
264                         }                               
265                 }
266         }
267         *dst = 0;
268         return str;
269 }
270
271 /* **************** data structure allocation **************** */
272
273 /*!
274  *\brief        Allocate a structure for a filtering / scoring
275  *              "condition" (a matcher structure)
276  *
277  *\param        criteria Criteria ID (MATCHCRITERIA_XXXX)
278  *\param        header Header string (if criteria is MATCHCRITERIA_HEADER)
279  *\param        matchtype Type of action (MATCHTYPE_XXX)
280  *\param        expr String value or expression to check
281  *\param        value Integer value to check
282  *
283  *\return       MatcherProp * Pointer to newly allocated structure
284  */
285 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
286                               gint matchtype, const gchar *expr,
287                               int value)
288 {
289         MatcherProp *prop;
290
291         prop = g_new0(MatcherProp, 1);
292         prop->criteria = criteria;
293         prop->header = header != NULL ? g_strdup(header) : NULL;
294         prop->expr = expr != NULL ? g_strdup(expr) : NULL;
295         prop->matchtype = matchtype;
296         prop->preg = NULL;
297         prop->value = value;
298         prop->error = 0;
299
300         return prop;
301 }
302
303 /*!
304  *\brief        Free a matcher structure
305  *
306  *\param        prop Pointer to matcher structure allocated with
307  *              #matcherprop_new
308  */
309 void matcherprop_free(MatcherProp *prop)
310 {
311         if (prop->expr) 
312                 g_free(prop->expr);
313         if (prop->header)
314                 g_free(prop->header);
315         if (prop->preg != NULL) {
316                 regfree(prop->preg);
317                 g_free(prop->preg);
318         }
319         g_free(prop);
320 }
321
322 /*!
323  *\brief        Copy a matcher structure
324  *
325  *\param        src Matcher structure to copy
326  *
327  *\return       MatcherProp * Pointer to newly allocated matcher structure
328  */
329 MatcherProp *matcherprop_copy(const MatcherProp *src)
330 {
331         MatcherProp *prop = g_new0(MatcherProp, 1);
332         
333         prop->criteria = src->criteria;
334         prop->header = src->header ? g_strdup(src->header) : NULL;
335         prop->expr = src->expr ? g_strdup(src->expr) : NULL;
336         prop->matchtype = src->matchtype;
337         
338         prop->preg = NULL; /* will be re-evaluated */
339         prop->value = src->value;
340         prop->error = src->error;       
341         return prop;            
342 }
343
344 /* ****************** wrapper for file reading ************** */
345
346 /*!
347  *\brief        Allocate a matcher structure where all strings
348  *              are unescaped ("unquoted")
349  *
350  *\param        criteria One of the MATCHCRITERIA_XXX constants
351  *\param        header A header string
352  *\param        matchtype Type of matcher (MATCHTYPE_XXX)
353  *\param        expr Matcher string expression
354  *\param        value Matcher integer value
355  *
356  *\return       MatcherProp * Pointer to newly allocated matcher
357  *              structure
358  */
359 MatcherProp *matcherprop_unquote_new(gint criteria, const gchar *header,
360                                      gint matchtype, const gchar *expr,
361                                      int value)
362 {
363         MatcherProp *prop;
364
365         if (expr != NULL)
366                 expr = matcher_unescape_str(g_strdup(expr));
367
368         if (header != NULL)
369                 header = matcher_unescape_str(g_strdup(header));
370         
371         prop = matcherprop_new(criteria, header, matchtype, expr, value);
372
373         g_free((gpointer) header);
374         g_free((gpointer) expr);
375
376         return prop;
377 }
378
379
380 /* ************** match ******************************/
381
382 /*!
383  *\brief        Find out if a string matches a condition
384  *
385  *\param        prop Matcher structure
386  *\param        str String to check 
387  *
388  *\return       gboolean TRUE if str matches the condition in the 
389  *              matcher structure
390  */
391 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str)
392 {
393         gchar *str1;
394         gchar *str2;
395
396         if (str == NULL)
397                 return FALSE;
398
399         switch (prop->matchtype) {
400         case MATCHTYPE_REGEXPCASE:
401         case MATCHTYPE_REGEXP:
402                 if (!prop->preg && (prop->error == 0)) {
403                         prop->preg = g_new0(regex_t, 1);
404                         /* if regexp then don't use the escaped string */
405                         if (regcomp(prop->preg, prop->expr,
406                                     REG_NOSUB | REG_EXTENDED
407                                     | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
408                                     ? REG_ICASE : 0)) != 0) {
409                                 prop->error = 1;
410                                 g_free(prop->preg);
411                                 prop->preg = NULL;
412                         }
413                 }
414                 if (prop->preg == NULL)
415                         return FALSE;
416                 
417                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
418                         return TRUE;
419                 else
420                         return FALSE;
421
422         case MATCHTYPE_MATCH:
423                 return (strstr(str, prop->expr) != NULL);
424
425         /* FIXME: put upper in unesc_str */
426         case MATCHTYPE_MATCHCASE:
427                 str2 = alloca(strlen(prop->expr) + 1);
428                 strcpy(str2, prop->expr);
429                 g_strup(str2);
430                 str1 = alloca(strlen(str) + 1);
431                 strcpy(str1, str);
432                 g_strup(str1);
433                 return (strstr(str1, str2) != NULL);
434                 
435         default:
436                 return FALSE;
437         }
438 }
439
440 /*!
441  *\brief        Execute a command defined in the matcher structure
442  *
443  *\param        prop Pointer to matcher structure
444  *\param        info Pointer to message info structure
445  *
446  *\return       gboolean TRUE if command was executed succesfully
447  */
448 static gboolean matcherprop_match_execute(const MatcherProp *prop, 
449                                           MsgInfo *info)
450 {
451         gchar *file;
452         gchar *cmd;
453         gint retval;
454
455         file = procmsg_get_message_file(info);
456         if (file == NULL)
457                 return FALSE;
458         g_free(file);           
459
460         cmd = matching_build_command(prop->expr, info);
461         if (cmd == NULL)
462                 return FALSE;
463
464         retval = system(cmd);
465         debug_print("Command exit code: %d\n", retval);
466
467         g_free(cmd);
468         return (retval == 0);
469 }
470
471 /*!
472  *\brief        Check if a message matches the condition in a matcher
473  *              structure.
474  *
475  *\param        prop Pointer to matcher structure
476  *\param        info Pointer to message info
477  *
478  *\return       gboolean TRUE if a match
479  */
480 gboolean matcherprop_match(MatcherProp *prop, 
481                            MsgInfo *info)
482 {
483         time_t t;
484
485         switch(prop->criteria) {
486         case MATCHCRITERIA_ALL:
487                 return 1;
488         case MATCHCRITERIA_UNREAD:
489                 return MSG_IS_UNREAD(info->flags);
490         case MATCHCRITERIA_NOT_UNREAD:
491                 return !MSG_IS_UNREAD(info->flags);
492         case MATCHCRITERIA_NEW:
493                 return MSG_IS_NEW(info->flags);
494         case MATCHCRITERIA_NOT_NEW:
495                 return !MSG_IS_NEW(info->flags);
496         case MATCHCRITERIA_MARKED:
497                 return MSG_IS_MARKED(info->flags);
498         case MATCHCRITERIA_NOT_MARKED:
499                 return !MSG_IS_MARKED(info->flags);
500         case MATCHCRITERIA_DELETED:
501                 return MSG_IS_DELETED(info->flags);
502         case MATCHCRITERIA_NOT_DELETED:
503                 return !MSG_IS_DELETED(info->flags);
504         case MATCHCRITERIA_REPLIED:
505                 return MSG_IS_REPLIED(info->flags);
506         case MATCHCRITERIA_NOT_REPLIED:
507                 return !MSG_IS_REPLIED(info->flags);
508         case MATCHCRITERIA_FORWARDED:
509                 return MSG_IS_FORWARDED(info->flags);
510         case MATCHCRITERIA_NOT_FORWARDED:
511                 return !MSG_IS_FORWARDED(info->flags);
512         case MATCHCRITERIA_LOCKED:
513                 return MSG_IS_LOCKED(info->flags);
514         case MATCHCRITERIA_NOT_LOCKED:
515                 return !MSG_IS_LOCKED(info->flags);
516         case MATCHCRITERIA_COLORLABEL:
517                 return MSG_GET_COLORLABEL_VALUE(info->flags) == prop->value; 
518         case MATCHCRITERIA_NOT_COLORLABEL:
519                 return MSG_GET_COLORLABEL_VALUE(info->flags) != prop->value;
520         case MATCHCRITERIA_IGNORE_THREAD:
521                 return MSG_IS_IGNORE_THREAD(info->flags);
522         case MATCHCRITERIA_NOT_IGNORE_THREAD:
523                 return !MSG_IS_IGNORE_THREAD(info->flags);
524         case MATCHCRITERIA_SUBJECT:
525                 return matcherprop_string_match(prop, info->subject);
526         case MATCHCRITERIA_NOT_SUBJECT:
527                 return !matcherprop_string_match(prop, info->subject);
528         case MATCHCRITERIA_FROM:
529                 return matcherprop_string_match(prop, info->from);
530         case MATCHCRITERIA_NOT_FROM:
531                 return !matcherprop_string_match(prop, info->from);
532         case MATCHCRITERIA_TO:
533                 return matcherprop_string_match(prop, info->to);
534         case MATCHCRITERIA_NOT_TO:
535                 return !matcherprop_string_match(prop, info->to);
536         case MATCHCRITERIA_CC:
537                 return matcherprop_string_match(prop, info->cc);
538         case MATCHCRITERIA_NOT_CC:
539                 return !matcherprop_string_match(prop, info->cc);
540         case MATCHCRITERIA_TO_OR_CC:
541                 return matcherprop_string_match(prop, info->to)
542                         || matcherprop_string_match(prop, info->cc);
543         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
544                 return !(matcherprop_string_match(prop, info->to)
545                 || matcherprop_string_match(prop, info->cc));
546         case MATCHCRITERIA_AGE_GREATER:
547                 t = time(NULL);
548                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
549         case MATCHCRITERIA_AGE_LOWER:
550                 t = time(NULL);
551                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
552         case MATCHCRITERIA_SCORE_GREATER:
553                 return info->score >= prop->value;
554         case MATCHCRITERIA_SCORE_LOWER:
555                 return info->score <= prop->value;
556         case MATCHCRITERIA_SCORE_EQUAL:
557                 return info->score == prop->value;
558         case MATCHCRITERIA_SIZE_GREATER:
559                 /* FIXME: info->size is an off_t */
560                 return info->size > (off_t) prop->value;
561         case MATCHCRITERIA_SIZE_EQUAL:
562                 /* FIXME: info->size is an off_t */
563                 return info->size == (off_t) prop->value;
564         case MATCHCRITERIA_SIZE_SMALLER:
565                 /* FIXME: info->size is an off_t */
566                 return info->size <  (off_t) prop->value;
567         case MATCHCRITERIA_NEWSGROUPS:
568                 return matcherprop_string_match(prop, info->newsgroups);
569         case MATCHCRITERIA_NOT_NEWSGROUPS:
570                 return !matcherprop_string_match(prop, info->newsgroups);
571         case MATCHCRITERIA_INREPLYTO:
572                 return matcherprop_string_match(prop, info->inreplyto);
573         case MATCHCRITERIA_NOT_INREPLYTO:
574                 return !matcherprop_string_match(prop, info->inreplyto);
575         case MATCHCRITERIA_REFERENCES:
576                 return matcherprop_string_match(prop, info->references);
577         case MATCHCRITERIA_NOT_REFERENCES:
578                 return !matcherprop_string_match(prop, info->references);
579         case MATCHCRITERIA_EXECUTE:
580                 return matcherprop_match_execute(prop, info);
581         case MATCHCRITERIA_NOT_EXECUTE:
582                 return !matcherprop_match_execute(prop, info);
583         default:
584                 return 0;
585         }
586 }
587
588 /* ********************* MatcherList *************************** */
589
590 /*!
591  *\brief        Create a new list of matchers 
592  *
593  *\param        matchers List of matcher structures
594  *\param        bool_and Operator
595  *
596  *\return       MatcherList * New list
597  */
598 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
599 {
600         MatcherList *cond;
601
602         cond = g_new0(MatcherList, 1);
603
604         cond->matchers = matchers;
605         cond->bool_and = bool_and;
606
607         return cond;
608 }
609
610 /*!
611  *\brief        Frees a list of matchers
612  *
613  *\param        cond List of matchers
614  */
615 void matcherlist_free(MatcherList *cond)
616 {
617         GSList *l;
618
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 }