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