scoring dialog box finished
[claws.git] / src / matcher.c
1 #include <ctype.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include "utils.h"
6 #include "defs.h"
7 #include "procheader.h"
8 #include "matcher.h"
9
10 struct _MatchParser {
11         gint id;
12         gchar * str;
13 };
14
15 typedef struct _MatchParser MatchParser;
16
17 MatchParser matchparser_tab[] = {
18         /* msginfo flags */
19         {MATCHING_ALL, "all"},
20         {MATCHING_UNREAD, "unread"},
21         {MATCHING_NOT_UNREAD, "~unread"},
22         {MATCHING_NEW, "new"},
23         {MATCHING_NOT_NEW, "~new"},
24         {MATCHING_MARKED, "marked"},
25         {MATCHING_NOT_MARKED, "~marked"},
26         {MATCHING_DELETED, "deleted"},
27         {MATCHING_NOT_DELETED, "~deleted"},
28         {MATCHING_REPLIED, "replied"},
29         {MATCHING_NOT_REPLIED, "~replied"},
30         {MATCHING_FORWARDED, "forwarded"},
31         {MATCHING_NOT_FORWARDED, "~forwarded"},
32
33         /* msginfo headers */
34         {MATCHING_SUBJECT, "subject"},
35         {MATCHING_NOT_SUBJECT, "~subject"},
36         {MATCHING_FROM, "from"},
37         {MATCHING_NOT_FROM, "~from"},
38         {MATCHING_TO, "to"},
39         {MATCHING_NOT_TO, "~to"},
40         {MATCHING_CC, "cc"},
41         {MATCHING_NOT_CC, "~cc"},
42         {MATCHING_TO_OR_CC, "to_or_cc"},
43         {MATCHING_NOT_TO_AND_NOT_CC, "~to_or_cc"},
44         {MATCHING_AGE_GREATER, "age_greater"},
45         {MATCHING_AGE_LOWER, "age_lower"},
46         {MATCHING_NEWSGROUPS, "newsgroups"},
47         {MATCHING_NOT_NEWSGROUPS, "~newsgroups"},
48
49         /* content have to be read */
50         {MATCHING_HEADER, "header"},
51         {MATCHING_NOT_HEADER, "~header"},
52         {MATCHING_HEADERS_PART, "headers_part"},
53         {MATCHING_NOT_HEADERS_PART, "~headers_part"},
54         {MATCHING_MESSAGE, "message"},
55         {MATCHING_NOT_MESSAGE, "~message"},
56         {MATCHING_BODY_PART, "body_part"},
57         {MATCHING_NOT_BODY_PART, "~body_part"},
58
59         /* match type */
60         {MATCHING_MATCHCASE, "matchcase"},
61         {MATCHING_MATCH, "match"},
62         {MATCHING_REGEXPCASE, "regexpcase"},
63         {MATCHING_REGEXP, "regexp"},
64
65         /* actions */
66         {MATCHING_SCORE, "score"},
67 };
68
69 /*
70   syntax for matcher
71
72   header "x-mailing" match "toto"
73   subject match "regexp" & to regexp "regexp"
74   subject match "regexp" | to regexpcase "regexp" | age_sup 5
75  */
76
77 static gboolean matcher_is_blank(gchar ch);
78
79 /* ******************* parser *********************** */
80
81 static gboolean matcher_is_blank(gchar ch)
82 {
83         return (ch == ' ') || (ch == '\t');
84 }
85
86 /* parse for one condition */
87
88 MatcherProp * matcherprop_parse(gchar ** str)
89 {
90         MatcherProp * prop;
91         gchar * tmp;
92         gint key;
93         gint age;
94         gchar * expr;
95         gint match;
96         gchar * header = NULL;
97         
98         tmp = * str;
99         key = matcher_parse_keyword(&tmp);
100         if (tmp == NULL) {
101                 * str = NULL;
102                 return NULL;
103         }
104
105         switch (key) {
106         case MATCHING_AGE_LOWER:
107         case MATCHING_AGE_GREATER:
108                 age = matcher_parse_number(&tmp);
109                 if (tmp == NULL) {
110                         * str = NULL;
111                         return NULL;
112                 }
113                 *str = tmp;
114
115                 prop = matcherprop_new(key, NULL, 0, NULL, age);
116
117                 return prop;
118
119         case MATCHING_ALL:
120         case MATCHING_UNREAD:
121         case MATCHING_NOT_UNREAD:
122         case MATCHING_NEW:
123         case MATCHING_NOT_NEW:
124         case MATCHING_MARKED:
125         case MATCHING_NOT_MARKED:
126         case MATCHING_DELETED:
127         case MATCHING_NOT_DELETED:
128         case MATCHING_REPLIED:
129         case MATCHING_NOT_REPLIED:
130         case MATCHING_FORWARDED:
131         case MATCHING_NOT_FORWARDED:
132                 prop = matcherprop_new(key, NULL, 0, NULL, 0);
133                 *str = tmp;
134
135                 return prop;
136
137         case MATCHING_SUBJECT:
138         case MATCHING_NOT_SUBJECT:
139         case MATCHING_FROM:
140         case MATCHING_NOT_FROM:
141         case MATCHING_TO:
142         case MATCHING_NOT_TO:
143         case MATCHING_CC:
144         case MATCHING_NOT_CC:
145         case MATCHING_TO_OR_CC:
146         case MATCHING_NOT_TO_AND_NOT_CC:
147         case MATCHING_NEWSGROUPS:
148         case MATCHING_NOT_NEWSGROUPS:
149         case MATCHING_MESSAGE:
150         case MATCHING_NOT_MESSAGE:
151         case MATCHING_HEADERS_PART:
152         case MATCHING_NOT_HEADERS_PART:
153         case MATCHING_BODY_PART:
154         case MATCHING_NOT_BODY_PART:
155         case MATCHING_HEADER:
156         case MATCHING_NOT_HEADER:
157                 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
158                         header = matcher_parse_str(&tmp);
159                         if (tmp == NULL) {
160                                 * str = NULL;
161                                 return NULL;
162                         }
163                 }
164
165                 match = matcher_parse_keyword(&tmp);
166                 if (tmp == NULL) {
167                         if (header)
168                                 g_free(header);
169                         * str = NULL;
170                         return NULL;
171                 }
172
173                 switch(match) {
174                 case MATCHING_REGEXP:
175                 case MATCHING_REGEXPCASE:
176                         expr = matcher_parse_regexp(&tmp);
177                         if (tmp == NULL) {
178                                 if (header)
179                                         g_free(header);
180                                 * str = NULL;
181                                 return NULL;
182                         }
183                         *str = tmp;
184                         prop = matcherprop_new(key, header, match, expr, 0);
185                         g_free(expr);
186
187                         return prop;
188                 case MATCHING_MATCH:
189                 case MATCHING_MATCHCASE:
190                         expr = matcher_parse_str(&tmp);
191                         if (tmp == NULL) {
192                                 if (header)
193                                         g_free(header);
194                                 * str = NULL;
195                                 return NULL;
196                         }
197                         *str = tmp;
198                         prop = matcherprop_new(key, header, match, expr, 0);
199                         g_free(expr);
200
201                         return prop;
202                 default:
203                         if (header)
204                                 g_free(header);
205                         * str = NULL;
206                         return NULL;
207                 }
208         default:
209                 * str = NULL;
210                 return NULL;
211         }
212 }
213
214 gint matcher_parse_keyword(gchar ** str)
215 {
216         gchar * p;
217         gchar * dup;
218         gchar * start;
219         gint i;
220         gint match;
221
222         dup = alloca(strlen(* str) + 1);
223         p = dup;
224         strcpy(dup, * str);
225
226         while (matcher_is_blank(*p))
227                 p++;
228
229         start = p;
230
231         while (!matcher_is_blank(*p) && (*p != '\0'))
232                 p++;
233         
234         match = -1;
235         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
236             i++) {
237                 if (strncasecmp(matchparser_tab[i].str, start,
238                                 p - start) == 0) {
239                         match = i;
240                         break;
241                 }
242         }
243
244         if (match == -1) {
245                 * str = NULL;
246                 return 0;
247         }
248
249         *p = '\0';
250
251         *str += p - dup + 1;
252         return matchparser_tab[match].id;
253 }
254
255 gint matcher_parse_number(gchar ** str)
256 {
257         gchar * p;
258         gchar * dup;
259         gchar * start;
260
261         dup = alloca(strlen(* str) + 1);
262         p = dup;
263         strcpy(dup, * str);
264
265         while (matcher_is_blank(*p))
266                 p++;
267
268         start = p;
269
270         if (!isdigit(*p) && *p != '-' && *p != '+') {
271                 *str = NULL;
272                 return 0;
273         }
274         if (*p == '-' || *p == '+')
275                 p++;
276         while (isdigit(*p))
277                 p++;
278
279         *p = '\0';
280
281         *str += p - dup + 1;
282         return atoi(start);
283 }
284
285 gboolean matcher_parse_boolean_op(gchar ** str)
286 {
287         gchar * p;
288
289         p = * str;
290
291         while (matcher_is_blank(*p))
292                 p++;
293
294         if (*p == '|') {
295                 *str += p - * str + 1;
296                 return FALSE;
297         }
298         else if (*p == '&') {
299                 *str += p - * str + 1;
300                 return TRUE;
301         }
302         else {
303                 *str = NULL;
304                 return FALSE;
305         }
306 }
307
308 gchar * matcher_parse_regexp(gchar ** str)
309 {
310         gchar * p;
311         gchar * dup;
312         gchar * start;
313
314         dup = alloca(strlen(* str) + 1);
315         p = dup;
316         strcpy(dup, * str);
317
318         while (matcher_is_blank(*p))
319                 p++;
320
321         if (*p != '/') {
322                 * str = NULL;
323                 return NULL;
324         }
325
326         p ++;
327         start = p;
328         while (*p != '/') {
329                 if (*p == '\\')
330                         p++;
331                 p++;
332         }
333         *p = '\0';
334
335         *str += p - dup + 2;
336         return g_strdup(start);
337 }
338
339 gchar * matcher_parse_str(gchar ** str)
340 {
341         gchar * p;
342         gchar * dup;
343         gchar * start;
344         gchar * dest;
345
346         dup = alloca(strlen(* str) + 1);
347         p = dup;
348         strcpy(dup, * str);
349
350         while (matcher_is_blank(*p))
351                 p++;
352
353         if (*p != '"') {
354                 * str = NULL;
355                 return NULL;
356         }
357         
358         p ++;
359         start = p;
360         dest = p;
361         while (*p != '"') {
362                 if (*p == '\\') {
363                         p++;
364                         *dest = *p;
365                 }
366                 else
367                         *dest = *p;
368                 dest++;
369                 p++;
370         }
371         *dest = '\0';
372
373         *str += dest - dup + 2;
374         return g_strdup(start);
375 }
376
377 /* **************** data structure allocation **************** */
378
379
380 MatcherProp * matcherprop_new(gint criteria, gchar * header,
381                               gint matchtype, gchar * expr,
382                               int age)
383 {
384         MatcherProp * prop;
385
386         prop = g_new0(MatcherProp, 1);
387         prop->criteria = criteria;
388         if (header != NULL)
389                 prop->header = g_strdup(header);
390         else
391                 prop->header = NULL;
392         if (expr != NULL)
393                 prop->expr = g_strdup(expr);
394         else
395                 prop->expr = NULL;
396         prop->matchtype = matchtype;
397         prop->preg = NULL;
398         prop->age = age;
399         prop->error = 0;
400
401         return prop;
402 }
403
404 void matcherprop_free(MatcherProp * prop)
405 {
406         g_free(prop->expr);
407         if (prop->preg != NULL) {
408                 regfree(prop->preg);
409                 g_free(prop->preg);
410         }
411         g_free(prop);
412 }
413
414
415 /* ************** match ******************************/
416
417
418 /* match the given string */
419
420 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
421 {
422         gchar * str1;
423         gchar * str2;
424
425         if (str == NULL)
426                 return FALSE;
427
428         switch(prop->matchtype) {
429         case MATCHING_REGEXP:
430         case MATCHING_REGEXPCASE:
431                 if (!prop->preg && (prop->error == 0)) {
432                         prop->preg = g_new0(regex_t, 1);
433                         if (regcomp(prop->preg, prop->expr,
434                                     REG_NOSUB | REG_EXTENDED
435                                     | ((prop->matchtype == MATCHING_REGEXPCASE)
436                                     ? REG_ICASE : 0)) != 0) {
437                                 prop->error = 1;
438                                 g_free(prop->preg);
439                         }
440                 }
441                 if (prop->preg == NULL)
442                         return FALSE;
443                 
444                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
445                         return TRUE;
446                 else
447                         return FALSE;
448
449         case MATCHING_MATCH:
450                 return (strstr(str, prop->expr) != NULL);
451
452         case MATCHING_MATCHCASE:
453                 str2 = alloca(strlen(prop->expr) + 1);
454                 strcpy(str2, prop->expr);
455                 g_strup(str2);
456                 str1 = alloca(strlen(str) + 1);
457                 strcpy(str1, str);
458                 g_strup(str1);
459                 return (strstr(str1, str2) != NULL);
460                 
461         default:
462                 return FALSE;
463         }
464 }
465
466 /* match a message and his headers, hlist can be NULL if you don't
467    want to use headers */
468
469 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
470 {
471         time_t t;
472
473         switch(prop->criteria) {
474         case MATCHING_ALL:
475                 return 1;
476         case MATCHING_UNREAD:
477                 return MSG_IS_UNREAD(info->flags);
478         case MATCHING_NOT_UNREAD:
479                 return !MSG_IS_UNREAD(info->flags);
480         case MATCHING_NEW:
481                 return MSG_IS_NEW(info->flags);
482         case MATCHING_NOT_NEW:
483                 return !MSG_IS_NEW(info->flags);
484         case MATCHING_MARKED:
485                 return MSG_IS_MARKED(info->flags);
486         case MATCHING_NOT_MARKED:
487                 return !MSG_IS_MARKED(info->flags);
488         case MATCHING_DELETED:
489                 return MSG_IS_DELETED(info->flags);
490         case MATCHING_NOT_DELETED:
491                 return !MSG_IS_DELETED(info->flags);
492         case MATCHING_REPLIED:
493                 return MSG_IS_REPLIED(info->flags);
494         case MATCHING_NOT_REPLIED:
495                 return !MSG_IS_REPLIED(info->flags);
496         case MATCHING_FORWARDED:
497                 return MSG_IS_FORWARDED(info->flags);
498         case MATCHING_NOT_FORWARDED:
499                 return !MSG_IS_FORWARDED(info->flags);
500         case MATCHING_SUBJECT:
501                 return matcherprop_string_match(prop, info->subject);
502         case MATCHING_NOT_SUBJECT:
503                 return !matcherprop_string_match(prop, info->subject);
504         case MATCHING_FROM:
505                 return matcherprop_string_match(prop, info->from);
506         case MATCHING_NOT_FROM:
507                 return !matcherprop_string_match(prop, info->from);
508         case MATCHING_TO:
509                 return matcherprop_string_match(prop, info->to);
510         case MATCHING_NOT_TO:
511                 return !matcherprop_string_match(prop, info->to);
512         case MATCHING_CC:
513                 return matcherprop_string_match(prop, info->cc);
514         case MATCHING_NOT_CC:
515                 return !matcherprop_string_match(prop, info->cc);
516         case MATCHING_TO_OR_CC:
517                 return matcherprop_string_match(prop, info->to)
518                         || matcherprop_string_match(prop, info->cc);
519         case MATCHING_NOT_TO_AND_NOT_CC:
520                 return !(matcherprop_string_match(prop, info->to)
521                 || matcherprop_string_match(prop, info->cc));
522         case MATCHING_AGE_GREATER:
523                 t = time(NULL);
524                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->age;
525         case MATCHING_AGE_LOWER:
526                 t = time(NULL);
527                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->age;
528         case MATCHING_NEWSGROUPS:
529                 return matcherprop_string_match(prop, info->newsgroups);
530         case MATCHING_NOT_NEWSGROUPS:
531                 return !matcherprop_string_match(prop, info->newsgroups);
532         case MATCHING_HEADER:
533         default:
534                 return 0;
535         }
536 }
537
538 /* ********************* MatcherList *************************** */
539
540
541 /* parse for a list of conditions */
542
543 MatcherList * matcherlist_parse(gchar ** str)
544 {
545         gchar * tmp;
546         MatcherProp * matcher;
547         GSList * matchers_list = NULL;
548         gboolean bool_and = TRUE;
549         gchar * save;
550         MatcherList * cond;
551         gboolean main_bool_and = TRUE;
552         GSList * l;
553
554         tmp = * str;
555
556         matcher = matcherprop_parse(&tmp);
557
558         if (tmp == NULL) {
559                 * str = NULL;
560                 return NULL;
561         }
562         matchers_list = g_slist_append(matchers_list, matcher);
563         while (matcher) {
564                 save = tmp;
565                 bool_and = matcher_parse_boolean_op(&tmp);
566                 if (tmp == NULL) {
567                         tmp = save;
568                         matcher = NULL;
569                 }
570                 else {
571                         main_bool_and = bool_and;
572                         matcher = matcherprop_parse(&tmp);
573                         if (tmp != NULL) {
574                                 matchers_list =
575                                         g_slist_append(matchers_list, matcher);
576                         }
577                         else {
578                                 for(l = matchers_list ; l != NULL ;
579                                     l = g_slist_next(l))
580                                         matcherprop_free((MatcherProp *)
581                                                          l->data);
582                                 g_slist_free(matchers_list);
583                                 * str = NULL;
584                                 return NULL;
585                         }
586                 }
587         }
588
589         cond = matcherlist_new(matchers_list, main_bool_and);
590
591         * str = tmp;
592
593         return cond;
594 }
595
596 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
597 {
598         MatcherList * cond;
599
600         cond = g_new0(MatcherList, 1);
601
602         cond->matchers = matchers;
603         cond->bool_and = bool_and;
604
605         return cond;
606 }
607
608 void matcherlist_free(MatcherList * cond)
609 {
610         GSList * l;
611
612         for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
613                 matcherprop_free((MatcherProp *) l->data);
614         }
615         g_free(cond);
616 }
617
618 /*
619   skip the headers
620  */
621
622 static void matcherlist_skip_headers(FILE *fp)
623 {
624         gchar buf[BUFFSIZE];
625
626         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
627         }
628 }
629
630 /*
631   matcherprop_match_one_header
632   returns TRUE if buf matchs the MatchersProp criteria
633  */
634
635 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
636                                              gchar * buf)
637 {
638         gboolean result;
639         Header *header;
640
641         switch(matcher->criteria) {
642         case MATCHING_HEADER:
643         case MATCHING_NOT_HEADER:
644                 header = procheader_parse_header(buf);
645                 if (procheader_headername_equal(header->name,
646                                                 matcher->header)) {
647                         if (matcher->criteria == MATCHING_HEADER)
648                                 result = matcherprop_string_match(matcher, header->body);
649                         else
650                                 result = !matcherprop_string_match(matcher, header->body);
651                         procheader_header_free(header);
652                         return result;
653                 }
654                 break;
655         case MATCHING_HEADERS_PART:
656         case MATCHING_MESSAGE:
657                 return matcherprop_string_match(matcher, buf);
658         case MATCHING_NOT_MESSAGE:
659         case MATCHING_NOT_HEADERS_PART:
660                 return !matcherprop_string_match(matcher, buf);
661         }
662         return FALSE;
663 }
664
665 /*
666   matcherprop_criteria_header
667   returns TRUE if the headers must be matched
668  */
669
670 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
671 {
672         switch(matcher->criteria) {
673         case MATCHING_HEADER:
674         case MATCHING_NOT_HEADER:
675         case MATCHING_HEADERS_PART:
676         case MATCHING_NOT_HEADERS_PART:
677         case MATCHING_MESSAGE:
678         case MATCHING_NOT_MESSAGE:
679                 return TRUE;
680         default:
681                 return FALSE;
682         }
683 }
684
685 /*
686   matcherlist_match_one_header
687   returns TRUE if buf matchs the MatchersList criteria
688  */
689
690 static gboolean matcherlist_match_one_header(MatcherList * matchers,
691                                              gchar * buf, gboolean result)
692 {
693         GSList * l;
694         
695         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
696                 MatcherProp * matcher = (MatcherProp *) l->data;
697
698                 if (matcherprop_criteria_headers(matcher)) {
699                         if (matcherprop_match_one_header(matcher, buf)) {
700                                 if (!matchers->bool_and)
701                                         return TRUE;
702                         }
703                         else {
704                                 if (matchers->bool_and)
705                                         return FALSE;
706                         }
707                 }
708         }
709
710         return result;
711 }
712
713 /*
714   matcherlist_match_headers
715   returns TRUE if one of the headers matchs the MatcherList criteria
716  */
717
718 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp,
719                                           gboolean result)
720 {
721         gchar buf[BUFFSIZE];
722
723         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
724                 if (matcherlist_match_one_header(matchers, buf, result)) {
725                         if (!matchers->bool_and)
726                                 return TRUE;
727                 }
728                 else {
729                         if (matchers->bool_and)
730                                 return FALSE;
731                 }
732         }
733         return result;
734 }
735
736 /*
737   matcherprop_criteria_body
738   returns TRUE if the body must be matched
739  */
740
741 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
742 {
743         switch(matcher->criteria) {
744         case MATCHING_BODY_PART:
745         case MATCHING_NOT_BODY_PART:
746         case MATCHING_MESSAGE:
747         case MATCHING_NOT_MESSAGE:
748                 return TRUE;
749         default:
750                 return FALSE;
751         }
752 }
753
754 /*
755   matcherprop_match_line
756   returns TRUE if the string matchs the MatcherProp criteria
757  */
758
759 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
760 {
761         switch(matcher->criteria) {
762         case MATCHING_BODY_PART:
763         case MATCHING_MESSAGE:
764                 return matcherprop_string_match(matcher, line);
765         case MATCHING_NOT_BODY_PART:
766         case MATCHING_NOT_MESSAGE:
767                 return !matcherprop_string_match(matcher, line);
768         }
769         return FALSE;
770 }
771
772 /*
773   matcherlist_match_line
774   returns TRUE if the string matchs the MatcherList criteria
775  */
776
777 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line,
778                                        gboolean result)
779 {
780         GSList * l;
781
782         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
783                 MatcherProp * matcher = (MatcherProp *) l->data;
784
785                 if (matcherprop_criteria_body(matcher)) {
786                         if (matcherprop_match_line(matcher, line)) {
787                                 if (!matchers->bool_and)
788                                         return TRUE;
789                         }
790                         else {
791                                 if (matchers->bool_and)
792                                         return FALSE;
793                         }
794                 }
795         }
796         return result;
797 }
798
799 /*
800   matcherlist_match_body
801   returns TRUE if one line of the body matchs the MatcherList criteria
802  */
803
804 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp,
805                                        gboolean result)
806 {
807         gchar buf[BUFFSIZE];
808
809         while (fgets(buf, sizeof(buf), fp) != NULL) {
810                 if (matcherlist_match_line(matchers, buf, result)) {
811                         if (!matchers->bool_and)
812                                 return TRUE;
813                 }
814                 else {
815                         if (matchers->bool_and)
816                                 return FALSE;
817                 }
818         }
819         return result;
820 }
821
822 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
823                                 gboolean result)
824 {
825         gboolean read_headers;
826         gboolean read_body;
827         GSList * l;
828         FILE * fp;
829         gchar * file;
830
831         /* file need to be read ? */
832
833         read_headers = FALSE;
834         read_body = FALSE;
835         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
836                 MatcherProp * matcher = (MatcherProp *) l->data;
837
838                 if (matcherprop_criteria_headers(matcher))
839                         read_headers = TRUE;
840                 if (matcherprop_criteria_body(matcher))
841                         read_body = TRUE;
842         }
843
844         if (!read_headers && !read_body)
845                 return result;
846
847         file = procmsg_get_message_file(info);
848         if (file == NULL)
849                 return FALSE;
850
851         if ((fp = fopen(file, "r")) == NULL) {
852                 FILE_OP_ERROR(file, "fopen");
853                 g_free(file);
854                 return result;
855         }
856         
857         /* read the headers */
858
859         if (read_headers) {
860                 if (matcherlist_match_headers(matchers, fp, result)) {
861                         if (!matchers->bool_and)
862                                 result = TRUE;
863                 }
864                 else {
865                         if (matchers->bool_and)
866                                 result = FALSE;
867                 }
868         }
869         else {
870                 matcherlist_skip_headers(fp);
871         }
872         
873         /* read the body */
874         if (read_body) {
875                 if (matcherlist_match_body(matchers, fp, result)) {
876                         if (!matchers->bool_and)
877                                 result = TRUE;
878                 }
879                 else {
880                         if (matchers->bool_and)
881                                 result = FALSE;
882                 }
883         }
884
885         g_free(file);
886
887         fclose(fp);
888         
889         return result;
890 }
891
892 /* test a list of condition */
893
894 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
895 {
896         GSList * l;
897         gboolean result;
898
899         if (matchers->bool_and)
900                 result = TRUE;
901         else
902                 result = FALSE;
903
904         /* test the condition on the file */
905
906         if (matcherlist_match_file(matchers, info, result)) {
907                 if (!matchers->bool_and)
908                         return TRUE;
909         }
910         else {
911                 if (matchers->bool_and)
912                         return FALSE;
913         }
914
915         /* test the cached elements */
916
917         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
918                 MatcherProp * matcher = (MatcherProp *) l->data;
919
920                 if (matcherprop_match(matcher, info)) {
921                         if (!matchers->bool_and) {
922                                 result = TRUE;
923                                 break;
924                         }
925                 }
926                 else {
927                         if (matchers->bool_and) {
928                                 result = FALSE;
929                                 break;
930                         }
931                 }
932         }
933
934         return result;
935 }
936
937 #ifdef 0
938 static void matcherprop_print(MatcherProp * matcher)
939 {
940   int i;
941
942         if (matcher == NULL) {
943                 printf("no matcher\n");
944                 return;
945         }
946
947         switch (matcher->matchtype) {
948         case MATCHING_MATCH:
949                 printf("match\n");
950                 break;
951         case MATCHING_REGEXP:
952                 printf("regexp\n");
953                 break;
954         case MATCHING_MATCHCASE:
955                 printf("matchcase\n");
956                 break;
957         case MATCHING_REGEXPCASE:
958                 printf("regexpcase\n");
959                 break;
960         }
961
962         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
963             i++) {
964                 if (matchparser_tab[i].id == matcher->criteria)
965                         printf("%s\n", matchparser_tab[i].str);
966         }
967
968         if (matcher->expr)
969                 printf("expr : %s\n", matcher->expr);
970
971         printf("age: %i\n", matcher->age);
972
973         printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no");
974         printf("error: %i\n",  matcher->error);
975 }
976 #endif
977
978 gchar * matcherprop_to_string(MatcherProp * matcher)
979 {
980         gchar * matcher_str = NULL;
981         gchar * criteria_str;
982         gchar * matchtype_str;
983         int i;
984         gchar * p;
985         gint count;
986         gchar * expr_str;
987         gchar * out;
988
989         criteria_str = NULL;
990         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
991             i++) {
992                 if (matchparser_tab[i].id == matcher->criteria)
993                         criteria_str = matchparser_tab[i].str;
994         }
995         if (criteria_str == NULL)
996                 return NULL;
997
998         switch(matcher->criteria) {
999         case MATCHING_AGE_GREATER:
1000         case MATCHING_AGE_LOWER:
1001                 return g_strdup_printf("%s %i", criteria_str, matcher->age);
1002                 break;
1003         case MATCHING_ALL:
1004         case MATCHING_UNREAD:
1005         case MATCHING_NOT_UNREAD:
1006         case MATCHING_NEW:
1007         case MATCHING_NOT_NEW:
1008         case MATCHING_MARKED:
1009         case MATCHING_NOT_MARKED:
1010         case MATCHING_DELETED:
1011         case MATCHING_NOT_DELETED:
1012         case MATCHING_REPLIED:
1013         case MATCHING_NOT_REPLIED:
1014         case MATCHING_FORWARDED:
1015         case MATCHING_NOT_FORWARDED:
1016                 return g_strdup(criteria_str);
1017         }
1018
1019         matchtype_str = NULL;
1020         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1021             i++) {
1022                 if (matchparser_tab[i].id == matcher->matchtype)
1023                         matchtype_str = matchparser_tab[i].str;
1024         }
1025
1026         if (matchtype_str == NULL)
1027                 return NULL;
1028
1029         switch (matcher->matchtype) {
1030         case MATCHING_MATCH:
1031         case MATCHING_MATCHCASE:
1032                 count = 0;
1033                 for(p = matcher->expr; *p != 0 ; p++)
1034                         if (*p == '\"') count ++;
1035                 
1036                 expr_str = g_new(char, strlen(matcher->expr) + count + 1);
1037
1038                 for(p = matcher->expr, out = expr_str ; *p != 0 ; p++, out++) {
1039                         if (*p == '\"') {
1040                                 *out = '\\'; out++;
1041                                 *out = '\"';
1042                         }
1043                         else
1044                                 *out = *p;
1045                 }
1046                 * out = '\0';
1047
1048                 if (matcher->header)
1049                         matcher_str =
1050                                 g_strdup_printf("%s \"%s\" %s \"%s\"",
1051                                            criteria_str, matcher->header,
1052                                            matchtype_str, expr_str);
1053                 else
1054                         matcher_str =
1055                                 g_strdup_printf("%s %s \"%s\"", criteria_str,
1056                                                 matchtype_str, expr_str);
1057                 
1058                 g_free(expr_str);
1059                 
1060                 break;
1061
1062         case MATCHING_REGEXP:
1063         case MATCHING_REGEXPCASE:
1064
1065                 if (matcher->header)
1066                         matcher_str =
1067                                 g_strdup_printf("%s \"%s\" %s /%s/",
1068                                                 criteria_str, matcher->header,
1069                                                 matchtype_str, matcher->expr);
1070                 else
1071                         matcher_str =
1072                                 g_strdup_printf("%s %s /%s/", criteria_str,
1073                                                 matchtype_str, matcher->expr);
1074
1075                 break;
1076         }
1077
1078         return matcher_str;
1079 }
1080
1081 gchar * matcherlist_to_string(MatcherList * matchers)
1082 {
1083         gint count;
1084         gchar ** vstr;
1085         GSList * l;
1086         gchar ** cur_str;
1087         gchar * result;
1088
1089         count = g_slist_length(matchers->matchers);
1090         vstr = g_new(gchar *, count + 1);
1091
1092         for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
1093              l = g_slist_next(l), cur_str ++) {
1094                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1095                 if (*cur_str == NULL)
1096                         break;
1097         }
1098         *cur_str = NULL;
1099         
1100         if (matchers->bool_and)
1101                 result = g_strjoinv(" & ", vstr);
1102         else
1103                 result = g_strjoinv(" | ", vstr);
1104
1105         for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1106                 g_free(*cur_str);
1107         g_free(vstr);
1108
1109         return result;
1110 }