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