memory leak fixes
[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 #include "intl.h"
10
11 struct _MatchParser {
12         gint id;
13         gchar * str;
14 };
15
16 typedef struct _MatchParser MatchParser;
17
18 static MatchParser matchparser_tab[] = {
19         /* msginfo flags */
20         {MATCHING_ALL, "all"},
21         {MATCHING_UNREAD, "unread"},
22         {MATCHING_NOT_UNREAD, "~unread"},
23         {MATCHING_NEW, "new"},
24         {MATCHING_NOT_NEW, "~new"},
25         {MATCHING_MARKED, "marked"},
26         {MATCHING_NOT_MARKED, "~marked"},
27         {MATCHING_DELETED, "deleted"},
28         {MATCHING_NOT_DELETED, "~deleted"},
29         {MATCHING_REPLIED, "replied"},
30         {MATCHING_NOT_REPLIED, "~replied"},
31         {MATCHING_FORWARDED, "forwarded"},
32         {MATCHING_NOT_FORWARDED, "~forwarded"},
33
34         /* msginfo headers */
35         {MATCHING_SUBJECT, "subject"},
36         {MATCHING_NOT_SUBJECT, "~subject"},
37         {MATCHING_FROM, "from"},
38         {MATCHING_NOT_FROM, "~from"},
39         {MATCHING_TO, "to"},
40         {MATCHING_NOT_TO, "~to"},
41         {MATCHING_CC, "cc"},
42         {MATCHING_NOT_CC, "~cc"},
43         {MATCHING_TO_OR_CC, "to_or_cc"},
44         {MATCHING_NOT_TO_AND_NOT_CC, "~to_or_cc"},
45         {MATCHING_AGE_GREATER, "age_greater"},
46         {MATCHING_AGE_LOWER, "age_lower"},
47         {MATCHING_NEWSGROUPS, "newsgroups"},
48         {MATCHING_NOT_NEWSGROUPS, "~newsgroups"},
49         {MATCHING_INREPLYTO, "inreplyto"},
50         {MATCHING_NOT_INREPLYTO, "~inreplyto"},
51         {MATCHING_REFERENCES, "references"},
52         {MATCHING_NOT_REFERENCES, "~references"},
53         {MATCHING_SCORE_GREATER, "score_greater"},
54         {MATCHING_SCORE_LOWER, "score_lower"},
55
56         /* content have to be read */
57         {MATCHING_HEADER, "header"},
58         {MATCHING_NOT_HEADER, "~header"},
59         {MATCHING_HEADERS_PART, "headers_part"},
60         {MATCHING_NOT_HEADERS_PART, "~headers_part"},
61         {MATCHING_MESSAGE, "message"},
62         {MATCHING_NOT_MESSAGE, "~message"},
63         {MATCHING_BODY_PART, "body_part"},
64         {MATCHING_NOT_BODY_PART, "~body_part"},
65         {MATCHING_EXECUTE, "execute"},
66         {MATCHING_NOT_EXECUTE, "~execute"},
67
68         /* match type */
69         {MATCHING_MATCHCASE, "matchcase"},
70         {MATCHING_MATCH, "match"},
71         {MATCHING_REGEXPCASE, "regexpcase"},
72         {MATCHING_REGEXP, "regexp"},
73
74         /* actions */
75         {MATCHING_SCORE, "score"},
76
77         /* actions */
78         {MATCHING_ACTION_MOVE, "move"},
79         {MATCHING_ACTION_COPY, "copy"},
80         {MATCHING_ACTION_DELETE, "delete"},
81         {MATCHING_ACTION_MARK, "mark"},
82         {MATCHING_ACTION_UNMARK, "unmark"},
83         {MATCHING_ACTION_MARK_AS_READ, "mark_as_read"},
84         {MATCHING_ACTION_MARK_AS_UNREAD, "mark_as_unread"},
85         {MATCHING_ACTION_FORWARD, "forward"},
86         {MATCHING_ACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
87         /*      {MATCHING_EXECUTE, "execute"}, */
88 };
89
90 gchar * get_matchparser_tab_str(gint id)
91 {
92         gint i;
93
94         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
95             i++) {
96                 if (matchparser_tab[i].id == id)
97                         return matchparser_tab[i].str;
98         }
99         return NULL;
100 }
101
102
103
104 /*
105   syntax for matcher
106
107   header "x-mailing" match "toto"
108   subject match "regexp" & to regexp "regexp"
109   subject match "regexp" | to regexpcase "regexp" | age_sup 5
110  */
111
112 static gboolean matcher_is_blank(gchar ch);
113
114 /* ******************* parser *********************** */
115
116 static gboolean matcher_is_blank(gchar ch)
117 {
118         return (ch == ' ') || (ch == '\t');
119 }
120
121 /* parse for one condition */
122
123 MatcherProp * matcherprop_parse(gchar ** str)
124 {
125         MatcherProp * prop;
126         gchar * tmp;
127         gint key;
128         gint value;
129         gchar * expr;
130         gint match;
131         gchar * header = NULL;
132         
133         tmp = * str;
134         key = matcher_parse_keyword(&tmp);
135         if (tmp == NULL) {
136                 * str = NULL;
137                 return NULL;
138         }
139
140         switch (key) {
141         case MATCHING_AGE_LOWER:
142         case MATCHING_AGE_GREATER:
143         case MATCHING_SCORE_LOWER:
144         case MATCHING_SCORE_GREATER:
145                 value = matcher_parse_number(&tmp);
146                 if (tmp == NULL) {
147                         * str = NULL;
148                         return NULL;
149                 }
150                 *str = tmp;
151
152                 prop = matcherprop_new(key, NULL, 0, NULL, value);
153
154                 return prop;
155
156         case MATCHING_ALL:
157         case MATCHING_UNREAD:
158         case MATCHING_NOT_UNREAD:
159         case MATCHING_NEW:
160         case MATCHING_NOT_NEW:
161         case MATCHING_MARKED:
162         case MATCHING_NOT_MARKED:
163         case MATCHING_DELETED:
164         case MATCHING_NOT_DELETED:
165         case MATCHING_REPLIED:
166         case MATCHING_NOT_REPLIED:
167         case MATCHING_FORWARDED:
168         case MATCHING_NOT_FORWARDED:
169                 prop = matcherprop_new(key, NULL, 0, NULL, 0);
170                 *str = tmp;
171
172                 return prop;
173
174         case MATCHING_SUBJECT:
175         case MATCHING_NOT_SUBJECT:
176         case MATCHING_FROM:
177         case MATCHING_NOT_FROM:
178         case MATCHING_TO:
179         case MATCHING_NOT_TO:
180         case MATCHING_CC:
181         case MATCHING_NOT_CC:
182         case MATCHING_TO_OR_CC:
183         case MATCHING_NOT_TO_AND_NOT_CC:
184         case MATCHING_NEWSGROUPS:
185         case MATCHING_NOT_NEWSGROUPS:
186         case MATCHING_INREPLYTO:
187         case MATCHING_NOT_REFERENCES:
188         case MATCHING_REFERENCES:
189         case MATCHING_NOT_INREPLYTO:
190         case MATCHING_MESSAGE:
191         case MATCHING_NOT_MESSAGE:
192         case MATCHING_EXECUTE:
193         case MATCHING_NOT_EXECUTE:
194         case MATCHING_HEADERS_PART:
195         case MATCHING_NOT_HEADERS_PART:
196         case MATCHING_BODY_PART:
197         case MATCHING_NOT_BODY_PART:
198         case MATCHING_HEADER:
199         case MATCHING_NOT_HEADER:
200                 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
201                         header = matcher_parse_str(&tmp);
202                         if (tmp == NULL) {
203                                 * str = NULL;
204                                 return NULL;
205                         }
206                 }
207
208                 match = matcher_parse_keyword(&tmp);
209                 if (tmp == NULL) {
210                         if (header)
211                                 g_free(header);
212                         * str = NULL;
213                         return NULL;
214                 }
215
216                 switch(match) {
217                 case MATCHING_REGEXP:
218                 case MATCHING_REGEXPCASE:
219                         expr = matcher_parse_regexp(&tmp);
220                         if (tmp == NULL) {
221                                 if (header)
222                                         g_free(header);
223                                 * str = NULL;
224                                 return NULL;
225                         }
226                         *str = tmp;
227                         prop = matcherprop_new(key, header, match, expr, 0);
228                         g_free(expr);
229
230                         return prop;
231                 case MATCHING_MATCH:
232                 case MATCHING_MATCHCASE:
233                         expr = matcher_parse_str(&tmp);
234                         if (tmp == NULL) {
235                                 if (header)
236                                         g_free(header);
237                                 * str = NULL;
238                                 return NULL;
239                         }
240                         *str = tmp;
241                         prop = matcherprop_new(key, header, match, expr, 0);
242                         g_free(expr);
243
244                         return prop;
245                 default:
246                         if (header)
247                                 g_free(header);
248                         * str = NULL;
249                         return NULL;
250                 }
251         default:
252                 * str = NULL;
253                 return NULL;
254         }
255 }
256
257 gint matcher_parse_keyword(gchar ** str)
258 {
259         gchar * p;
260         gchar * dup;
261         gchar * start;
262         gint i;
263         gint match;
264
265         dup = alloca(strlen(* str) + 1);
266         p = dup;
267         strcpy(dup, * str);
268
269         while (matcher_is_blank(*p))
270                 p++;
271
272         start = p;
273
274         while (!matcher_is_blank(*p) && (*p != '\0'))
275                 p++;
276         
277         match = -1;
278         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
279             i++) {
280                 if ((strlen(matchparser_tab[i].str) == p - start) &&
281                     (strncasecmp(matchparser_tab[i].str, start,
282                                  p - start) == 0)) {
283                                 match = i;
284                                 break;
285                         }
286         }
287
288         if (match == -1) {
289                 * str = NULL;
290                 return 0;
291         }
292
293         *p = '\0';
294
295         *str += p - dup + 1;
296         return matchparser_tab[match].id;
297 }
298
299 gint matcher_parse_number(gchar ** str)
300 {
301         gchar * p;
302         gchar * dup;
303         gchar * start;
304
305         dup = alloca(strlen(* str) + 1);
306         p = dup;
307         strcpy(dup, * str);
308
309         while (matcher_is_blank(*p))
310                 p++;
311
312         start = p;
313
314         if (!isdigit(*p) && *p != '-' && *p != '+') {
315                 *str = NULL;
316                 return 0;
317         }
318         if (*p == '-' || *p == '+')
319                 p++;
320         while (isdigit(*p))
321                 p++;
322
323         *p = '\0';
324
325         *str += p - dup + 1;
326         return atoi(start);
327 }
328
329 gboolean matcher_parse_boolean_op(gchar ** str)
330 {
331         gchar * p;
332
333         p = * str;
334
335         while (matcher_is_blank(*p))
336                 p++;
337
338         if (*p == '|') {
339                 *str += p - * str + 1;
340                 return FALSE;
341         }
342         else if (*p == '&') {
343                 *str += p - * str + 1;
344                 return TRUE;
345         }
346         else {
347                 *str = NULL;
348                 return FALSE;
349         }
350 }
351
352 gchar * matcher_parse_regexp(gchar ** str)
353 {
354         gchar * p;
355         gchar * dup;
356         gchar * start;
357
358         dup = alloca(strlen(* str) + 1);
359         p = dup;
360         strcpy(dup, * str);
361
362         while (matcher_is_blank(*p))
363                 p++;
364
365         if (*p != '/') {
366                 * str = NULL;
367                 return NULL;
368         }
369
370         p ++;
371         start = p;
372         while (*p != '/') {
373                 if (*p == '\\')
374                         p++;
375                 p++;
376         }
377         *p = '\0';
378
379         *str += p - dup + 2;
380         return g_strdup(start);
381 }
382
383 gchar * matcher_parse_str(gchar ** str)
384 {
385         gchar * p;
386         gchar * dup;
387         gchar * start;
388         gchar * dest;
389
390         dup = alloca(strlen(* str) + 1);
391         p = dup;
392         strcpy(dup, * str);
393
394         while (matcher_is_blank(*p))
395                 p++;
396
397         if (*p != '"') {
398                 * str = NULL;
399                 return NULL;
400         }
401         
402         p ++;
403         start = p;
404         dest = p;
405         while (*p != '"') {
406                 if (*p == '\\') {
407                         p++;
408                         *dest = *p;
409                 }
410                 else
411                         *dest = *p;
412                 dest++;
413                 p++;
414         }
415         *dest = '\0';
416
417         *str += dest - dup + 2;
418         return g_strdup(start);
419 }
420
421 /* **************** data structure allocation **************** */
422
423
424 MatcherProp * matcherprop_new(gint criteria, gchar * header,
425                               gint matchtype, gchar * expr,
426                               int value)
427 {
428         MatcherProp * prop;
429
430         prop = g_new0(MatcherProp, 1);
431         prop->criteria = criteria;
432         if (header != NULL)
433                 prop->header = g_strdup(header);
434         else
435                 prop->header = NULL;
436         if (expr != NULL)
437                 prop->expr = g_strdup(expr);
438         else
439                 prop->expr = NULL;
440         prop->matchtype = matchtype;
441         prop->preg = NULL;
442         prop->value = value;
443         prop->error = 0;
444
445         return prop;
446 }
447
448 void matcherprop_free(MatcherProp * prop)
449 {
450         g_free(prop->expr);
451         if (prop->preg != NULL) {
452                 regfree(prop->preg);
453                 g_free(prop->preg);
454         }
455         g_free(prop);
456 }
457
458
459 /* ************** match ******************************/
460
461
462 /* match the given string */
463
464 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
465 {
466         gchar * str1;
467         gchar * str2;
468
469         if (str == NULL)
470                 return FALSE;
471
472         switch(prop->matchtype) {
473         case MATCHING_REGEXPCASE:
474         case MATCHING_REGEXP:
475                 if (!prop->preg && (prop->error == 0)) {
476                         prop->preg = g_new0(regex_t, 1);
477                         if (regcomp(prop->preg, prop->expr,
478                                     REG_NOSUB | REG_EXTENDED
479                                     | ((prop->matchtype == MATCHING_REGEXPCASE)
480                                     ? REG_ICASE : 0)) != 0) {
481                                 prop->error = 1;
482                                 g_free(prop->preg);
483                         }
484                 }
485                 if (prop->preg == NULL)
486                         return FALSE;
487                 
488                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
489                         return TRUE;
490                 else
491                         return FALSE;
492
493         case MATCHING_MATCH:
494                 return (strstr(str, prop->expr) != NULL);
495
496         case MATCHING_MATCHCASE:
497                 str2 = alloca(strlen(prop->expr) + 1);
498                 strcpy(str2, prop->expr);
499                 g_strup(str2);
500                 str1 = alloca(strlen(str) + 1);
501                 strcpy(str1, str);
502                 g_strup(str1);
503                 return (strstr(str1, str2) != NULL);
504                 
505         default:
506                 return FALSE;
507         }
508 }
509
510 gboolean matcherprop_match_execute(MatcherProp * prop, MsgInfo * info)
511 {
512         gchar * file;
513         gchar * cmd;
514
515         file = procmsg_get_message_file(info);
516         if (file == NULL)
517                 return FALSE;
518
519         cmd = matching_build_command(prop->expr, info);
520         if (cmd == NULL)
521                 return FALSE;
522
523         return (system(cmd) == 0);
524 }
525
526 /* match a message and his headers, hlist can be NULL if you don't
527    want to use headers */
528
529 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
530 {
531         time_t t;
532
533         switch(prop->criteria) {
534         case MATCHING_ALL:
535                 return 1;
536         case MATCHING_UNREAD:
537                 return MSG_IS_UNREAD(info->flags);
538         case MATCHING_NOT_UNREAD:
539                 return !MSG_IS_UNREAD(info->flags);
540         case MATCHING_NEW:
541                 return MSG_IS_NEW(info->flags);
542         case MATCHING_NOT_NEW:
543                 return !MSG_IS_NEW(info->flags);
544         case MATCHING_MARKED:
545                 return MSG_IS_MARKED(info->flags);
546         case MATCHING_NOT_MARKED:
547                 return !MSG_IS_MARKED(info->flags);
548         case MATCHING_DELETED:
549                 return MSG_IS_DELETED(info->flags);
550         case MATCHING_NOT_DELETED:
551                 return !MSG_IS_DELETED(info->flags);
552         case MATCHING_REPLIED:
553                 return MSG_IS_REPLIED(info->flags);
554         case MATCHING_NOT_REPLIED:
555                 return !MSG_IS_REPLIED(info->flags);
556         case MATCHING_FORWARDED:
557                 return MSG_IS_FORWARDED(info->flags);
558         case MATCHING_NOT_FORWARDED:
559                 return !MSG_IS_FORWARDED(info->flags);
560         case MATCHING_SUBJECT:
561                 return matcherprop_string_match(prop, info->subject);
562         case MATCHING_NOT_SUBJECT:
563                 return !matcherprop_string_match(prop, info->subject);
564         case MATCHING_FROM:
565                 return matcherprop_string_match(prop, info->from);
566         case MATCHING_NOT_FROM:
567                 return !matcherprop_string_match(prop, info->from);
568         case MATCHING_TO:
569                 return matcherprop_string_match(prop, info->to);
570         case MATCHING_NOT_TO:
571                 return !matcherprop_string_match(prop, info->to);
572         case MATCHING_CC:
573                 return matcherprop_string_match(prop, info->cc);
574         case MATCHING_NOT_CC:
575                 return !matcherprop_string_match(prop, info->cc);
576         case MATCHING_TO_OR_CC:
577                 return matcherprop_string_match(prop, info->to)
578                         || matcherprop_string_match(prop, info->cc);
579         case MATCHING_NOT_TO_AND_NOT_CC:
580                 return !(matcherprop_string_match(prop, info->to)
581                 || matcherprop_string_match(prop, info->cc));
582         case MATCHING_AGE_GREATER:
583                 t = time(NULL);
584                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
585         case MATCHING_AGE_LOWER:
586                 t = time(NULL);
587                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
588         case MATCHING_SCORE_GREATER:
589                 return info->score >= prop->value;
590         case MATCHING_SCORE_LOWER:
591                 return info->score <= prop->value;
592         case MATCHING_NEWSGROUPS:
593                 return matcherprop_string_match(prop, info->newsgroups);
594         case MATCHING_NOT_NEWSGROUPS:
595                 return !matcherprop_string_match(prop, info->newsgroups);
596         case MATCHING_INREPLYTO:
597                 return matcherprop_string_match(prop, info->inreplyto);
598         case MATCHING_NOT_INREPLYTO:
599                 return !matcherprop_string_match(prop, info->inreplyto);
600         case MATCHING_REFERENCES:
601                 return matcherprop_string_match(prop, info->references);
602         case MATCHING_NOT_REFERENCES:
603                 return !matcherprop_string_match(prop, info->references);
604         case MATCHING_EXECUTE:
605                 return matcherprop_match_execute(prop, info);
606         case MATCHING_NOT_EXECUTE:
607                 return !matcherprop_match_execute(prop, info);
608         default:
609                 return 0;
610         }
611 }
612
613 /* ********************* MatcherList *************************** */
614
615
616 /* parse for a list of conditions */
617
618 MatcherList * matcherlist_parse(gchar ** str)
619 {
620         gchar * tmp;
621         MatcherProp * matcher;
622         GSList * matchers_list = NULL;
623         gboolean bool_and = TRUE;
624         gchar * save;
625         MatcherList * cond;
626         gboolean main_bool_and = TRUE;
627         GSList * l;
628
629         tmp = * str;
630
631         matcher = matcherprop_parse(&tmp);
632
633         if (tmp == NULL) {
634                 * str = NULL;
635                 return NULL;
636         }
637         matchers_list = g_slist_append(matchers_list, matcher);
638         while (matcher) {
639                 save = tmp;
640                 bool_and = matcher_parse_boolean_op(&tmp);
641                 if (tmp == NULL) {
642                         tmp = save;
643                         matcher = NULL;
644                 }
645                 else {
646                         main_bool_and = bool_and;
647                         matcher = matcherprop_parse(&tmp);
648                         if (tmp != NULL) {
649                                 matchers_list =
650                                         g_slist_append(matchers_list, matcher);
651                         }
652                         else {
653                                 for(l = matchers_list ; l != NULL ;
654                                     l = g_slist_next(l))
655                                         matcherprop_free((MatcherProp *)
656                                                          l->data);
657                                 g_slist_free(matchers_list);
658                                 * str = NULL;
659                                 return NULL;
660                         }
661                 }
662         }
663
664         cond = matcherlist_new(matchers_list, main_bool_and);
665
666         * str = tmp;
667
668         return cond;
669 }
670
671 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
672 {
673         MatcherList * cond;
674
675         cond = g_new0(MatcherList, 1);
676
677         cond->matchers = matchers;
678         cond->bool_and = bool_and;
679
680         return cond;
681 }
682
683 void matcherlist_free(MatcherList * cond)
684 {
685         GSList * l;
686
687         for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
688                 matcherprop_free((MatcherProp *) l->data);
689         }
690         g_free(cond);
691 }
692
693 /*
694   skip the headers
695  */
696
697 static void matcherlist_skip_headers(FILE *fp)
698 {
699         gchar buf[BUFFSIZE];
700
701         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
702         }
703 }
704
705 /*
706   matcherprop_match_one_header
707   returns TRUE if buf matchs the MatchersProp criteria
708  */
709
710 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
711                                              gchar * buf)
712 {
713         gboolean result;
714         Header *header;
715
716         switch(matcher->criteria) {
717         case MATCHING_HEADER:
718         case MATCHING_NOT_HEADER:
719                 header = procheader_parse_header(buf);
720                 if (!header)
721                         return FALSE;
722                 if (procheader_headername_equal(header->name,
723                                                 matcher->header)) {
724                         if (matcher->criteria == MATCHING_HEADER)
725                                 result = matcherprop_string_match(matcher, header->body);
726                         else
727                                 result = !matcherprop_string_match(matcher, header->body);
728                         procheader_header_free(header);
729                         return result;
730                 }
731                 else {
732                         procheader_header_free(header);
733                 }
734                 break;
735         case MATCHING_HEADERS_PART:
736         case MATCHING_MESSAGE:
737                 return matcherprop_string_match(matcher, buf);
738         case MATCHING_NOT_MESSAGE:
739         case MATCHING_NOT_HEADERS_PART:
740                 return !matcherprop_string_match(matcher, buf);
741         }
742         return FALSE;
743 }
744
745 /*
746   matcherprop_criteria_header
747   returns TRUE if the headers must be matched
748  */
749
750 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
751 {
752         switch(matcher->criteria) {
753         case MATCHING_HEADER:
754         case MATCHING_NOT_HEADER:
755         case MATCHING_HEADERS_PART:
756         case MATCHING_NOT_HEADERS_PART:
757                 return TRUE;
758         default:
759                 return FALSE;
760         }
761 }
762
763 static gboolean matcherprop_criteria_message(MatcherProp * matcher)
764 {
765         switch(matcher->criteria) {
766         case MATCHING_MESSAGE:
767         case MATCHING_NOT_MESSAGE:
768                 return TRUE;
769         default:
770                 return FALSE;
771         }
772 }
773
774 /*
775   matcherlist_match_one_header
776   returns TRUE if match should stop
777  */
778
779 static gboolean matcherlist_match_one_header(MatcherList * matchers,
780                                          gchar * buf)
781 {
782         GSList * l;
783
784         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
785                 MatcherProp * matcher = (MatcherProp *) l->data;
786
787                 if (matcherprop_criteria_headers(matcher) ||
788                     matcherprop_criteria_message(matcher)) {
789                         if (matcherprop_match_one_header(matcher, buf)) {
790                                 matcher->result = TRUE;
791                         }
792                 }
793
794                 if (matcherprop_criteria_headers(matcher)) {
795                         if (matcher->result) {
796                                 if (!matchers->bool_and)
797                                         return TRUE;
798                         }
799                 }
800         }
801
802         return FALSE;
803 }
804
805 /*
806   matcherlist_match_headers
807   returns TRUE if one of the headers matchs the MatcherList criteria
808  */
809
810 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp)
811 {
812         gchar buf[BUFFSIZE];
813
814         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
815                 if (matcherlist_match_one_header(matchers, buf))
816                         return TRUE;
817
818         return FALSE;
819 }
820
821 /*
822   matcherprop_criteria_body
823   returns TRUE if the body must be matched
824  */
825
826 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
827 {
828         switch(matcher->criteria) {
829         case MATCHING_BODY_PART:
830         case MATCHING_NOT_BODY_PART:
831                 return TRUE;
832         default:
833                 return FALSE;
834         }
835 }
836
837 /*
838   matcherprop_match_line
839   returns TRUE if the string matchs the MatcherProp criteria
840  */
841
842 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
843 {
844         switch(matcher->criteria) {
845         case MATCHING_BODY_PART:
846         case MATCHING_MESSAGE:
847                 return matcherprop_string_match(matcher, line);
848         case MATCHING_NOT_BODY_PART:
849         case MATCHING_NOT_MESSAGE:
850                 return !matcherprop_string_match(matcher, line);
851         }
852         return FALSE;
853 }
854
855 /*
856   matcherlist_match_line
857   returns TRUE if the string matchs the MatcherList criteria
858  */
859
860 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line)
861 {
862         GSList * l;
863
864         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
865                 MatcherProp * matcher = (MatcherProp *) l->data;
866
867                 if (matcherprop_criteria_body(matcher) ||
868                     matcherprop_criteria_message(matcher)) {
869                         if (matcherprop_match_line(matcher, line)) {
870                                 matcher->result = TRUE;
871                         }
872                 }
873                         
874                 if (matcher->result) {
875                         if (!matchers->bool_and)
876                                 return TRUE;
877                 }
878         }
879         return FALSE;
880 }
881
882 /*
883   matcherlist_match_body
884   returns TRUE if one line of the body matchs the MatcherList criteria
885  */
886
887 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp)
888 {
889         gchar buf[BUFFSIZE];
890
891         while (fgets(buf, sizeof(buf), fp) != NULL)
892                 if (matcherlist_match_line(matchers, buf))
893                         return TRUE;
894
895         return FALSE;
896 }
897
898 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
899                                 gboolean result)
900 {
901         gboolean read_headers;
902         gboolean read_body;
903         GSList * l;
904         FILE * fp;
905         gchar * file;
906
907         /* file need to be read ? */
908
909         read_headers = FALSE;
910         read_body = FALSE;
911         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
912                 MatcherProp * matcher = (MatcherProp *) l->data;
913
914                 if (matcherprop_criteria_headers(matcher))
915                         read_headers = TRUE;
916                 if (matcherprop_criteria_body(matcher))
917                         read_body = TRUE;
918                 if (matcherprop_criteria_message(matcher)) {
919                         read_headers = TRUE;
920                         read_body = TRUE;
921                 }
922                 matcher->result = FALSE;
923         }
924
925         if (!read_headers && !read_body)
926                 return result;
927
928         file = procmsg_get_message_file(info);
929         if (file == NULL)
930                 return FALSE;
931
932         if ((fp = fopen(file, "r")) == NULL) {
933                 FILE_OP_ERROR(file, "fopen");
934                 g_free(file);
935                 return result;
936         }
937
938         /* read the headers */
939
940         if (read_headers) {
941                 if (matcherlist_match_headers(matchers, fp))
942                         read_body = FALSE;
943         }
944         else {
945                 matcherlist_skip_headers(fp);
946         }
947
948         /* read the body */
949         if (read_body) {
950                 matcherlist_match_body(matchers, fp);
951         }
952         
953         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
954                 MatcherProp * matcher = (MatcherProp *) l->data;
955
956                 if (matcherprop_criteria_headers(matcher) ||
957                     matcherprop_criteria_body(matcher) ||
958                     matcherprop_criteria_message(matcher))
959                         if (matcher->result) {
960                                 if (!matchers->bool_and) {
961                                         result = TRUE;
962                                         break;
963                                 }
964                         }
965                         else {
966                                 if (matchers->bool_and) {
967                                         result = FALSE;
968                                         break;
969                                 }
970                 }
971         }
972
973         g_free(file);
974
975         fclose(fp);
976         
977         return result;
978 }
979
980 /* test a list of condition */
981
982 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
983 {
984         GSList * l;
985         gboolean result;
986
987         if (matchers->bool_and)
988                 result = TRUE;
989         else
990                 result = FALSE;
991
992         /* test the cached elements */
993
994         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
995                 MatcherProp * matcher = (MatcherProp *) l->data;
996
997                 switch(matcher->criteria) {
998                 case MATCHING_ALL:
999                 case MATCHING_UNREAD:
1000                 case MATCHING_NOT_UNREAD:
1001                 case MATCHING_NEW:
1002                 case MATCHING_NOT_NEW:
1003                 case MATCHING_MARKED:
1004                 case MATCHING_NOT_MARKED:
1005                 case MATCHING_DELETED:
1006                 case MATCHING_NOT_DELETED:
1007                 case MATCHING_REPLIED:
1008                 case MATCHING_NOT_REPLIED:
1009                 case MATCHING_FORWARDED:
1010                 case MATCHING_NOT_FORWARDED:
1011                 case MATCHING_SUBJECT:
1012                 case MATCHING_NOT_SUBJECT:
1013                 case MATCHING_FROM:
1014                 case MATCHING_NOT_FROM:
1015                 case MATCHING_TO:
1016                 case MATCHING_NOT_TO:
1017                 case MATCHING_CC:
1018                 case MATCHING_NOT_CC:
1019                 case MATCHING_TO_OR_CC:
1020                 case MATCHING_NOT_TO_AND_NOT_CC:
1021                 case MATCHING_AGE_GREATER:
1022                 case MATCHING_AGE_LOWER:
1023                 case MATCHING_NEWSGROUPS:
1024                 case MATCHING_NOT_NEWSGROUPS:
1025                 case MATCHING_INREPLYTO:
1026                 case MATCHING_NOT_INREPLYTO:
1027                 case MATCHING_REFERENCES:
1028                 case MATCHING_NOT_REFERENCES:
1029                 case MATCHING_SCORE_GREATER:
1030                 case MATCHING_SCORE_LOWER:
1031                 case MATCHING_EXECUTE:
1032                 case MATCHING_NOT_EXECUTE:
1033                         if (matcherprop_match(matcher, info)) {
1034                                 if (!matchers->bool_and) {
1035                                         return TRUE;
1036                                 }
1037                         }
1038                         else {
1039                                 if (matchers->bool_and) {
1040                                         return FALSE;
1041                                 }
1042                         }
1043                 }
1044         }
1045
1046         /* test the condition on the file */
1047
1048         if (matcherlist_match_file(matchers, info, result)) {
1049                 if (!matchers->bool_and)
1050                         return TRUE;
1051         }
1052         else {
1053                 if (matchers->bool_and)
1054                         return FALSE;
1055         }
1056
1057         return result;
1058 }
1059
1060 #if 0
1061 static void matcherprop_print(MatcherProp * matcher)
1062 {
1063   int i;
1064
1065         if (matcher == NULL) {
1066                 printf("no matcher\n");
1067                 return;
1068         }
1069
1070         switch (matcher->matchtype) {
1071         case MATCHING_MATCH:
1072                 printf("match\n");
1073                 break;
1074         case MATCHING_REGEXP:
1075                 printf("regexp\n");
1076                 break;
1077         case MATCHING_MATCHCASE:
1078                 printf("matchcase\n");
1079                 break;
1080         case MATCHING_REGEXPCASE:
1081                 printf("regexpcase\n");
1082                 break;
1083         }
1084
1085         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1086             i++) {
1087                 if (matchparser_tab[i].id == matcher->criteria)
1088                         printf("%s\n", matchparser_tab[i].str);
1089         }
1090
1091         if (matcher->expr)
1092                 printf("expr : %s\n", matcher->expr);
1093
1094         printf("age: %i\n", matcher->value;
1095
1096         printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no");
1097         printf("error: %i\n",  matcher->error);
1098 }
1099 #endif
1100
1101 gchar * matcherprop_to_string(MatcherProp * matcher)
1102 {
1103         gchar * matcher_str = NULL;
1104         gchar * criteria_str;
1105         gchar * matchtype_str;
1106         int i;
1107         gchar * p;
1108         gint count;
1109         gchar * expr_str;
1110         gchar * out;
1111
1112         criteria_str = NULL;
1113         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1114             i++) {
1115                 if (matchparser_tab[i].id == matcher->criteria)
1116                         criteria_str = matchparser_tab[i].str;
1117         }
1118         if (criteria_str == NULL)
1119                 return NULL;
1120
1121         switch(matcher->criteria) {
1122         case MATCHING_AGE_GREATER:
1123         case MATCHING_AGE_LOWER:
1124         case MATCHING_SCORE_GREATER:
1125         case MATCHING_SCORE_LOWER:
1126                 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1127                 break;
1128         case MATCHING_ALL:
1129         case MATCHING_UNREAD:
1130         case MATCHING_NOT_UNREAD:
1131         case MATCHING_NEW:
1132         case MATCHING_NOT_NEW:
1133         case MATCHING_MARKED:
1134         case MATCHING_NOT_MARKED:
1135         case MATCHING_DELETED:
1136         case MATCHING_NOT_DELETED:
1137         case MATCHING_REPLIED:
1138         case MATCHING_NOT_REPLIED:
1139         case MATCHING_FORWARDED:
1140         case MATCHING_NOT_FORWARDED:
1141                 return g_strdup(criteria_str);
1142         }
1143
1144         matchtype_str = NULL;
1145         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1146             i++) {
1147                 if (matchparser_tab[i].id == matcher->matchtype)
1148                         matchtype_str = matchparser_tab[i].str;
1149         }
1150
1151         if (matchtype_str == NULL)
1152                 return NULL;
1153
1154         switch (matcher->matchtype) {
1155         case MATCHING_MATCH:
1156         case MATCHING_MATCHCASE:
1157                 count = 0;
1158                 for(p = matcher->expr; *p != 0 ; p++)
1159                         if (*p == '\"') count ++;
1160                 
1161                 expr_str = g_new(char, strlen(matcher->expr) + count + 1);
1162
1163                 for(p = matcher->expr, out = expr_str ; *p != 0 ; p++, out++) {
1164                         if (*p == '\"') {
1165                                 *out = '\\'; out++;
1166                                 *out = '\"';
1167                         }
1168                         else
1169                                 *out = *p;
1170                 }
1171                 * out = '\0';
1172
1173                 if (matcher->header)
1174                         matcher_str =
1175                                 g_strdup_printf("%s \"%s\" %s \"%s\"",
1176                                            criteria_str, matcher->header,
1177                                            matchtype_str, expr_str);
1178                 else
1179                         matcher_str =
1180                                 g_strdup_printf("%s %s \"%s\"", criteria_str,
1181                                                 matchtype_str, expr_str);
1182                 
1183                 g_free(expr_str);
1184                 
1185                 break;
1186
1187         case MATCHING_REGEXP:
1188         case MATCHING_REGEXPCASE:
1189
1190                 if (matcher->header)
1191                         matcher_str =
1192                                 g_strdup_printf("%s \"%s\" %s /%s/",
1193                                                 criteria_str, matcher->header,
1194                                                 matchtype_str, matcher->expr);
1195                 else
1196                         matcher_str =
1197                                 g_strdup_printf("%s %s /%s/", criteria_str,
1198                                                 matchtype_str, matcher->expr);
1199
1200                 break;
1201         }
1202
1203         return matcher_str;
1204 }
1205
1206 gchar * matcherlist_to_string(MatcherList * matchers)
1207 {
1208         gint count;
1209         gchar ** vstr;
1210         GSList * l;
1211         gchar ** cur_str;
1212         gchar * result;
1213
1214         count = g_slist_length(matchers->matchers);
1215         vstr = g_new(gchar *, count + 1);
1216
1217         for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
1218              l = g_slist_next(l), cur_str ++) {
1219                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1220                 if (*cur_str == NULL)
1221                         break;
1222         }
1223         *cur_str = NULL;
1224         
1225         if (matchers->bool_and)
1226                 result = g_strjoinv(" & ", vstr);
1227         else
1228                 result = g_strjoinv(" | ", vstr);
1229
1230         for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1231                 g_free(*cur_str);
1232         g_free(vstr);
1233
1234         return result;
1235 }
1236
1237
1238 gchar * matching_build_command(gchar * cmd, MsgInfo * info)
1239 {
1240         gchar * s = cmd;
1241         gchar * filename = NULL;
1242         gchar * processed_cmd;
1243         gchar * p;
1244         gint size;
1245
1246         size = strlen(cmd) + 1;
1247         while (*s != '\0') {
1248                 if (*s == '%') {
1249                         s++;
1250                         switch (*s) {
1251                         case '%':
1252                                 size -= 1;
1253                                 break;
1254                         case 's': /* subject */
1255                                 size += strlen(info->subject) - 2;
1256                                 break;
1257                         case 'f': /* from */
1258                                 size += strlen(info->from) - 2;
1259                                 break;
1260                         case 't': /* to */
1261                                 size += strlen(info->to) - 2;
1262                                 break;
1263                         case 'c': /* cc */
1264                                 size += strlen(info->cc) - 2;
1265                                 break;
1266                         case 'd': /* date */
1267                                 size += strlen(info->date) - 2;
1268                                 break;
1269                         case 'i': /* message-id */
1270                                 size += strlen(info->msgid) - 2;
1271                                 break;
1272                         case 'n': /* newsgroups */
1273                                 size += strlen(info->newsgroups) - 2;
1274                                 break;
1275                         case 'r': /* references */
1276                                 size += strlen(info->references) - 2;
1277                                 break;
1278                         case 'F': /* file */
1279                                 filename = folder_item_fetch_msg(info->folder,
1280                                                                  info->msgnum);
1281                                 
1282                                 if (filename == NULL) {
1283                                         g_warning(_("filename is not set"));
1284                                         return NULL;
1285                                 }
1286                                 else
1287                                         size += strlen(filename) - 2;
1288                                 break;
1289                         }
1290                         s++;
1291                 }
1292                 else s++;
1293         }
1294
1295
1296         processed_cmd = g_new0(gchar, size);
1297         s = cmd;
1298         p = processed_cmd;
1299
1300         while (*s != '\0') {
1301                 if (*s == '%') {
1302                         s++;
1303                         switch (*s) {
1304                         case '%':
1305                                 *p = '%';
1306                                 p++;
1307                                 break;
1308                         case 's': /* subject */
1309                                 if (info->subject != NULL)
1310                                         strcpy(p, info->subject);
1311                                 else
1312                                         strcpy(p, "(none)");
1313                                 p += strlen(p);
1314                                 break;
1315                         case 'f': /* from */
1316                                 if (info->from != NULL)
1317                                         strcpy(p, info->from);
1318                                 else
1319                                         strcpy(p, "(none)");
1320                                 p += strlen(p);
1321                                 break;
1322                         case 't': /* to */
1323                                 if (info->to != NULL)
1324                                         strcpy(p, info->to);
1325                                 else
1326                                         strcpy(p, "(none)");
1327                                 p += strlen(p);
1328                                 break;
1329                         case 'c': /* cc */
1330                                 if (info->cc != NULL)
1331                                         strcpy(p, info->cc);
1332                                 else
1333                                         strcpy(p, "(none)");
1334                                 p += strlen(p);
1335                                 break;
1336                         case 'd': /* date */
1337                                 if (info->date != NULL)
1338                                         strcpy(p, info->date);
1339                                 else
1340                                         strcpy(p, "(none)");
1341                                 p += strlen(p);
1342                                 break;
1343                         case 'i': /* message-id */
1344                                 if (info->msgid != NULL)
1345                                         strcpy(p, info->msgid);
1346                                 else
1347                                         strcpy(p, "(none)");
1348                                 p += strlen(p);
1349                                 break;
1350                         case 'n': /* newsgroups */
1351                                 if (info->newsgroups != NULL)
1352                                         strcpy(p, info->newsgroups);
1353                                 else
1354                                         strcpy(p, "(none)");
1355                                 p += strlen(p);
1356                                 break;
1357                         case 'r': /* references */
1358                                 if (info->references != NULL)
1359                                         strcpy(p, info->references);
1360                                 else
1361                                         strcpy(p, "(none)");
1362                                 p += strlen(p);
1363                                 break;
1364                         case 'F': /* file */
1365                                 strcpy(p, filename);
1366                                 p += strlen(p);
1367                                 break;
1368                         default:
1369                                 *p = '%';
1370                                 p++;
1371                                 *p = *s;
1372                                 p++;
1373                                 break;
1374                         }
1375                         s++;
1376                 }
1377                 else {
1378                         *p = *s;
1379                         p++;
1380                         s++;
1381                 }
1382         }
1383         return processed_cmd;
1384 }
1385
1386
1387 /* ************************************************************ */
1388
1389 /*
1390 static void matcher_parse               (gchar * str)
1391 {
1392         matcher_parser_scan_string(str);
1393         matcher_parserparse();
1394 }
1395 */