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