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