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