e6308eb52699ba72133635374b4bff9ebfdf638e
[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_ATTACHMENT, "forward_as_attachment"},
84 };
85
86 gchar * get_matchparser_tab_str(gint id)
87 {
88         gint i;
89
90         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
91             i++) {
92                 if (matchparser_tab[i].id == id)
93                         return matchparser_tab[i].str;
94         }
95         return NULL;
96 }
97
98
99
100 /*
101   syntax for matcher
102
103   header "x-mailing" match "toto"
104   subject match "regexp" & to regexp "regexp"
105   subject match "regexp" | to regexpcase "regexp" | age_sup 5
106  */
107
108 static gboolean matcher_is_blank(gchar ch);
109
110 /* ******************* parser *********************** */
111
112 static gboolean matcher_is_blank(gchar ch)
113 {
114         return (ch == ' ') || (ch == '\t');
115 }
116
117 /* parse for one condition */
118
119 MatcherProp * matcherprop_parse(gchar ** str)
120 {
121         MatcherProp * prop;
122         gchar * tmp;
123         gint key;
124         gint value;
125         gchar * expr;
126         gint match;
127         gchar * header = NULL;
128         
129         tmp = * str;
130         key = matcher_parse_keyword(&tmp);
131         if (tmp == NULL) {
132                 * str = NULL;
133                 return NULL;
134         }
135
136         switch (key) {
137         case MATCHING_AGE_LOWER:
138         case MATCHING_AGE_GREATER:
139         case MATCHING_SCORE_LOWER:
140         case MATCHING_SCORE_GREATER:
141                 value = matcher_parse_number(&tmp);
142                 if (tmp == NULL) {
143                         * str = NULL;
144                         return NULL;
145                 }
146                 *str = tmp;
147
148                 prop = matcherprop_new(key, NULL, 0, NULL, value);
149
150                 return prop;
151
152         case MATCHING_ALL:
153         case MATCHING_UNREAD:
154         case MATCHING_NOT_UNREAD:
155         case MATCHING_NEW:
156         case MATCHING_NOT_NEW:
157         case MATCHING_MARKED:
158         case MATCHING_NOT_MARKED:
159         case MATCHING_DELETED:
160         case MATCHING_NOT_DELETED:
161         case MATCHING_REPLIED:
162         case MATCHING_NOT_REPLIED:
163         case MATCHING_FORWARDED:
164         case MATCHING_NOT_FORWARDED:
165                 prop = matcherprop_new(key, NULL, 0, NULL, 0);
166                 *str = tmp;
167
168                 return prop;
169
170         case MATCHING_SUBJECT:
171         case MATCHING_NOT_SUBJECT:
172         case MATCHING_FROM:
173         case MATCHING_NOT_FROM:
174         case MATCHING_TO:
175         case MATCHING_NOT_TO:
176         case MATCHING_CC:
177         case MATCHING_NOT_CC:
178         case MATCHING_TO_OR_CC:
179         case MATCHING_NOT_TO_AND_NOT_CC:
180         case MATCHING_NEWSGROUPS:
181         case MATCHING_NOT_NEWSGROUPS:
182         case MATCHING_INREPLYTO:
183         case MATCHING_NOT_REFERENCES:
184         case MATCHING_REFERENCES:
185         case MATCHING_NOT_INREPLYTO:
186         case MATCHING_MESSAGE:
187         case MATCHING_NOT_MESSAGE:
188         case MATCHING_HEADERS_PART:
189         case MATCHING_NOT_HEADERS_PART:
190         case MATCHING_BODY_PART:
191         case MATCHING_NOT_BODY_PART:
192         case MATCHING_HEADER:
193         case MATCHING_NOT_HEADER:
194                 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
195                         header = matcher_parse_str(&tmp);
196                         if (tmp == NULL) {
197                                 * str = NULL;
198                                 return NULL;
199                         }
200                 }
201
202                 match = matcher_parse_keyword(&tmp);
203                 if (tmp == NULL) {
204                         if (header)
205                                 g_free(header);
206                         * str = NULL;
207                         return NULL;
208                 }
209
210                 switch(match) {
211                 case MATCHING_REGEXP:
212                 case MATCHING_REGEXPCASE:
213                         expr = matcher_parse_regexp(&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                 case MATCHING_MATCH:
226                 case MATCHING_MATCHCASE:
227                         expr = matcher_parse_str(&tmp);
228                         if (tmp == NULL) {
229                                 if (header)
230                                         g_free(header);
231                                 * str = NULL;
232                                 return NULL;
233                         }
234                         *str = tmp;
235                         prop = matcherprop_new(key, header, match, expr, 0);
236                         g_free(expr);
237
238                         return prop;
239                 default:
240                         if (header)
241                                 g_free(header);
242                         * str = NULL;
243                         return NULL;
244                 }
245         default:
246                 * str = NULL;
247                 return NULL;
248         }
249 }
250
251 gint matcher_parse_keyword(gchar ** str)
252 {
253         gchar * p;
254         gchar * dup;
255         gchar * start;
256         gint i;
257         gint match;
258
259         dup = alloca(strlen(* str) + 1);
260         p = dup;
261         strcpy(dup, * str);
262
263         while (matcher_is_blank(*p))
264                 p++;
265
266         start = p;
267
268         while (!matcher_is_blank(*p) && (*p != '\0'))
269                 p++;
270         
271         match = -1;
272         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
273             i++) {
274                 if ((strlen(matchparser_tab[i].str) == p - start) &&
275                     (strncasecmp(matchparser_tab[i].str, start,
276                                  p - start) == 0)) {
277                                 match = i;
278                                 break;
279                         }
280         }
281
282         if (match == -1) {
283                 * str = NULL;
284                 return 0;
285         }
286
287         *p = '\0';
288
289         *str += p - dup + 1;
290         return matchparser_tab[match].id;
291 }
292
293 gint matcher_parse_number(gchar ** str)
294 {
295         gchar * p;
296         gchar * dup;
297         gchar * start;
298
299         dup = alloca(strlen(* str) + 1);
300         p = dup;
301         strcpy(dup, * str);
302
303         while (matcher_is_blank(*p))
304                 p++;
305
306         start = p;
307
308         if (!isdigit(*p) && *p != '-' && *p != '+') {
309                 *str = NULL;
310                 return 0;
311         }
312         if (*p == '-' || *p == '+')
313                 p++;
314         while (isdigit(*p))
315                 p++;
316
317         *p = '\0';
318
319         *str += p - dup + 1;
320         return atoi(start);
321 }
322
323 gboolean matcher_parse_boolean_op(gchar ** str)
324 {
325         gchar * p;
326
327         p = * str;
328
329         while (matcher_is_blank(*p))
330                 p++;
331
332         if (*p == '|') {
333                 *str += p - * str + 1;
334                 return FALSE;
335         }
336         else if (*p == '&') {
337                 *str += p - * str + 1;
338                 return TRUE;
339         }
340         else {
341                 *str = NULL;
342                 return FALSE;
343         }
344 }
345
346 gchar * matcher_parse_regexp(gchar ** str)
347 {
348         gchar * p;
349         gchar * dup;
350         gchar * start;
351
352         dup = alloca(strlen(* str) + 1);
353         p = dup;
354         strcpy(dup, * str);
355
356         while (matcher_is_blank(*p))
357                 p++;
358
359         if (*p != '/') {
360                 * str = NULL;
361                 return NULL;
362         }
363
364         p ++;
365         start = p;
366         while (*p != '/') {
367                 if (*p == '\\')
368                         p++;
369                 p++;
370         }
371         *p = '\0';
372
373         *str += p - dup + 2;
374         return g_strdup(start);
375 }
376
377 gchar * matcher_parse_str(gchar ** str)
378 {
379         gchar * p;
380         gchar * dup;
381         gchar * start;
382         gchar * dest;
383
384         dup = alloca(strlen(* str) + 1);
385         p = dup;
386         strcpy(dup, * str);
387
388         while (matcher_is_blank(*p))
389                 p++;
390
391         if (*p != '"') {
392                 * str = NULL;
393                 return NULL;
394         }
395         
396         p ++;
397         start = p;
398         dest = p;
399         while (*p != '"') {
400                 if (*p == '\\') {
401                         p++;
402                         *dest = *p;
403                 }
404                 else
405                         *dest = *p;
406                 dest++;
407                 p++;
408         }
409         *dest = '\0';
410
411         *str += dest - dup + 2;
412         return g_strdup(start);
413 }
414
415 /* **************** data structure allocation **************** */
416
417
418 MatcherProp * matcherprop_new(gint criteria, gchar * header,
419                               gint matchtype, gchar * expr,
420                               int value)
421 {
422         MatcherProp * prop;
423
424         prop = g_new0(MatcherProp, 1);
425         prop->criteria = criteria;
426         if (header != NULL)
427                 prop->header = g_strdup(header);
428         else
429                 prop->header = NULL;
430         if (expr != NULL)
431                 prop->expr = g_strdup(expr);
432         else
433                 prop->expr = NULL;
434         prop->matchtype = matchtype;
435         prop->preg = NULL;
436         prop->value = value;
437         prop->error = 0;
438
439         return prop;
440 }
441
442 void matcherprop_free(MatcherProp * prop)
443 {
444         g_free(prop->expr);
445         if (prop->preg != NULL) {
446                 regfree(prop->preg);
447                 g_free(prop->preg);
448         }
449         g_free(prop);
450 }
451
452
453 /* ************** match ******************************/
454
455
456 /* match the given string */
457
458 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
459 {
460         gchar * str1;
461         gchar * str2;
462
463         if (str == NULL)
464                 return FALSE;
465
466         switch(prop->matchtype) {
467         case MATCHING_REGEXPCASE:
468         case MATCHING_REGEXP:
469                 if (!prop->preg && (prop->error == 0)) {
470                         prop->preg = g_new0(regex_t, 1);
471                         if (regcomp(prop->preg, prop->expr,
472                                     REG_NOSUB | REG_EXTENDED
473                                     | ((prop->matchtype == MATCHING_REGEXPCASE)
474                                     ? REG_ICASE : 0)) != 0) {
475                                 prop->error = 1;
476                                 g_free(prop->preg);
477                         }
478                 }
479                 if (prop->preg == NULL)
480                         return FALSE;
481                 
482                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
483                         return TRUE;
484                 else
485                         return FALSE;
486
487         case MATCHING_MATCH:
488                 return (strstr(str, prop->expr) != NULL);
489
490         case MATCHING_MATCHCASE:
491                 str2 = alloca(strlen(prop->expr) + 1);
492                 strcpy(str2, prop->expr);
493                 g_strup(str2);
494                 str1 = alloca(strlen(str) + 1);
495                 strcpy(str1, str);
496                 g_strup(str1);
497                 return (strstr(str1, str2) != NULL);
498                 
499         default:
500                 return FALSE;
501         }
502 }
503
504 /* match a message and his headers, hlist can be NULL if you don't
505    want to use headers */
506
507 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
508 {
509         time_t t;
510
511         switch(prop->criteria) {
512         case MATCHING_ALL:
513                 return 1;
514         case MATCHING_UNREAD:
515                 return MSG_IS_UNREAD(info->flags);
516         case MATCHING_NOT_UNREAD:
517                 return !MSG_IS_UNREAD(info->flags);
518         case MATCHING_NEW:
519                 return MSG_IS_NEW(info->flags);
520         case MATCHING_NOT_NEW:
521                 return !MSG_IS_NEW(info->flags);
522         case MATCHING_MARKED:
523                 return MSG_IS_MARKED(info->flags);
524         case MATCHING_NOT_MARKED:
525                 return !MSG_IS_MARKED(info->flags);
526         case MATCHING_DELETED:
527                 return MSG_IS_DELETED(info->flags);
528         case MATCHING_NOT_DELETED:
529                 return !MSG_IS_DELETED(info->flags);
530         case MATCHING_REPLIED:
531                 return MSG_IS_REPLIED(info->flags);
532         case MATCHING_NOT_REPLIED:
533                 return !MSG_IS_REPLIED(info->flags);
534         case MATCHING_FORWARDED:
535                 return MSG_IS_FORWARDED(info->flags);
536         case MATCHING_NOT_FORWARDED:
537                 return !MSG_IS_FORWARDED(info->flags);
538         case MATCHING_SUBJECT:
539                 return matcherprop_string_match(prop, info->subject);
540         case MATCHING_NOT_SUBJECT:
541                 return !matcherprop_string_match(prop, info->subject);
542         case MATCHING_FROM:
543                 return matcherprop_string_match(prop, info->from);
544         case MATCHING_NOT_FROM:
545                 return !matcherprop_string_match(prop, info->from);
546         case MATCHING_TO:
547                 return matcherprop_string_match(prop, info->to);
548         case MATCHING_NOT_TO:
549                 return !matcherprop_string_match(prop, info->to);
550         case MATCHING_CC:
551                 return matcherprop_string_match(prop, info->cc);
552         case MATCHING_NOT_CC:
553                 return !matcherprop_string_match(prop, info->cc);
554         case MATCHING_TO_OR_CC:
555                 return matcherprop_string_match(prop, info->to)
556                         || matcherprop_string_match(prop, info->cc);
557         case MATCHING_NOT_TO_AND_NOT_CC:
558                 return !(matcherprop_string_match(prop, info->to)
559                 || matcherprop_string_match(prop, info->cc));
560         case MATCHING_AGE_GREATER:
561                 t = time(NULL);
562                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
563         case MATCHING_AGE_LOWER:
564                 t = time(NULL);
565                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
566         case MATCHING_SCORE_GREATER:
567                 return info->score >= prop->value;
568         case MATCHING_SCORE_LOWER:
569                 return info->score <= prop->value;
570         case MATCHING_NEWSGROUPS:
571                 return matcherprop_string_match(prop, info->newsgroups);
572         case MATCHING_NOT_NEWSGROUPS:
573                 return !matcherprop_string_match(prop, info->newsgroups);
574         case MATCHING_INREPLYTO:
575                 return matcherprop_string_match(prop, info->inreplyto);
576         case MATCHING_NOT_INREPLYTO:
577                 return !matcherprop_string_match(prop, info->inreplyto);
578         case MATCHING_REFERENCES:
579                 return matcherprop_string_match(prop, info->references);
580         case MATCHING_NOT_REFERENCES:
581                 return !matcherprop_string_match(prop, info->references);
582         case MATCHING_HEADER:
583         default:
584                 return 0;
585         }
586 }
587
588 /* ********************* MatcherList *************************** */
589
590
591 /* parse for a list of conditions */
592
593 MatcherList * matcherlist_parse(gchar ** str)
594 {
595         gchar * tmp;
596         MatcherProp * matcher;
597         GSList * matchers_list = NULL;
598         gboolean bool_and = TRUE;
599         gchar * save;
600         MatcherList * cond;
601         gboolean main_bool_and = TRUE;
602         GSList * l;
603
604         tmp = * str;
605
606         matcher = matcherprop_parse(&tmp);
607
608         if (tmp == NULL) {
609                 * str = NULL;
610                 return NULL;
611         }
612         matchers_list = g_slist_append(matchers_list, matcher);
613         while (matcher) {
614                 save = tmp;
615                 bool_and = matcher_parse_boolean_op(&tmp);
616                 if (tmp == NULL) {
617                         tmp = save;
618                         matcher = NULL;
619                 }
620                 else {
621                         main_bool_and = bool_and;
622                         matcher = matcherprop_parse(&tmp);
623                         if (tmp != NULL) {
624                                 matchers_list =
625                                         g_slist_append(matchers_list, matcher);
626                         }
627                         else {
628                                 for(l = matchers_list ; l != NULL ;
629                                     l = g_slist_next(l))
630                                         matcherprop_free((MatcherProp *)
631                                                          l->data);
632                                 g_slist_free(matchers_list);
633                                 * str = NULL;
634                                 return NULL;
635                         }
636                 }
637         }
638
639         cond = matcherlist_new(matchers_list, main_bool_and);
640
641         * str = tmp;
642
643         return cond;
644 }
645
646 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
647 {
648         MatcherList * cond;
649
650         cond = g_new0(MatcherList, 1);
651
652         cond->matchers = matchers;
653         cond->bool_and = bool_and;
654
655         return cond;
656 }
657
658 void matcherlist_free(MatcherList * cond)
659 {
660         GSList * l;
661
662         for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
663                 matcherprop_free((MatcherProp *) l->data);
664         }
665         g_free(cond);
666 }
667
668 /*
669   skip the headers
670  */
671
672 static void matcherlist_skip_headers(FILE *fp)
673 {
674         gchar buf[BUFFSIZE];
675
676         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
677         }
678 }
679
680 /*
681   matcherprop_match_one_header
682   returns TRUE if buf matchs the MatchersProp criteria
683  */
684
685 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
686                                              gchar * buf)
687 {
688         gboolean result;
689         Header *header;
690
691         switch(matcher->criteria) {
692         case MATCHING_HEADER:
693         case MATCHING_NOT_HEADER:
694                 header = procheader_parse_header(buf);
695                 if (!header)
696                         return FALSE;
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 }