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