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