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