58b3fdb0a1f93c4b7fc2b6ca38bfb70721697c92
[claws.git] / src / matcher.c
1 #include <ctype.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include "defs.h"
6 #include "utils.h"
7 #include "procheader.h"
8 #include "matcher.h"
9 #include "intl.h"
10 #include "matcher_parser.h"
11 #include "prefs.h"
12
13 struct _MatchParser {
14         gint id;
15         gchar * str;
16 };
17
18 typedef struct _MatchParser MatchParser;
19
20 static MatchParser matchparser_tab[] = {
21         /* msginfo flags */
22         {MATCHCRITERIA_ALL, "all"},
23         {MATCHCRITERIA_UNREAD, "unread"},
24         {MATCHCRITERIA_NOT_UNREAD, "~unread"},
25         {MATCHCRITERIA_NEW, "new"},
26         {MATCHCRITERIA_NOT_NEW, "~new"},
27         {MATCHCRITERIA_MARKED, "marked"},
28         {MATCHCRITERIA_NOT_MARKED, "~marked"},
29         {MATCHCRITERIA_DELETED, "deleted"},
30         {MATCHCRITERIA_NOT_DELETED, "~deleted"},
31         {MATCHCRITERIA_REPLIED, "replied"},
32         {MATCHCRITERIA_NOT_REPLIED, "~replied"},
33         {MATCHCRITERIA_FORWARDED, "forwarded"},
34         {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
35
36         /* msginfo headers */
37         {MATCHCRITERIA_SUBJECT, "subject"},
38         {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
39         {MATCHCRITERIA_FROM, "from"},
40         {MATCHCRITERIA_NOT_FROM, "~from"},
41         {MATCHCRITERIA_TO, "to"},
42         {MATCHCRITERIA_NOT_TO, "~to"},
43         {MATCHCRITERIA_CC, "cc"},
44         {MATCHCRITERIA_NOT_CC, "~cc"},
45         {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
46         {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
47         {MATCHCRITERIA_AGE_GREATER, "age_greater"},
48         {MATCHCRITERIA_AGE_LOWER, "age_lower"},
49         {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
50         {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
51         {MATCHCRITERIA_INREPLYTO, "inreplyto"},
52         {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
53         {MATCHCRITERIA_REFERENCES, "references"},
54         {MATCHCRITERIA_NOT_REFERENCES, "~references"},
55         {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
56         {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
57         {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
58
59         /* content have to be read */
60         {MATCHCRITERIA_HEADER, "header"},
61         {MATCHCRITERIA_NOT_HEADER, "~header"},
62         {MATCHCRITERIA_HEADERS_PART, "headers_part"},
63         {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
64         {MATCHCRITERIA_MESSAGE, "message"},
65         {MATCHCRITERIA_NOT_MESSAGE, "~message"},
66         {MATCHCRITERIA_BODY_PART, "body_part"},
67         {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
68         {MATCHCRITERIA_EXECUTE, "execute"},
69         {MATCHCRITERIA_NOT_EXECUTE, "~execute"},
70
71         /* match type */
72         {MATCHTYPE_MATCHCASE, "matchcase"},
73         {MATCHTYPE_MATCH, "match"},
74         {MATCHTYPE_REGEXPCASE, "regexpcase"},
75         {MATCHTYPE_REGEXP, "regexp"},
76
77         /* actions */
78         {MATCHACTION_SCORE, "score"},
79         {MATCHACTION_MOVE, "move"},
80         {MATCHACTION_COPY, "copy"},
81         {MATCHACTION_DELETE, "delete"},
82         {MATCHACTION_MARK, "mark"},
83         {MATCHACTION_UNMARK, "unmark"},
84         {MATCHACTION_MARK_AS_READ, "mark_as_read"},
85         {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
86         {MATCHACTION_FORWARD, "forward"},
87         {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
88         {MATCHACTION_EXECUTE, "execute"},
89         {MATCHACTION_COLOR, "color"},
90         {MATCHACTION_BOUNCE, "bounce"}
91 };
92
93 /*
94   syntax for matcher
95
96   header "x-mailing" match "toto" score -10
97   subject match "regexp" & to regexp "regexp" score 50
98   subject match "regexp" | to regexpcase "regexp" | age_sup 5 score 30
99
100 */
101
102 gchar * get_matchparser_tab_str(gint id)
103 {
104         gint i;
105
106         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
107             i++) {
108                 if (matchparser_tab[i].id == id)
109                         return matchparser_tab[i].str;
110         }
111         return NULL;
112 }
113
114 /* matcher_unescape_str() - assumes that unescaping frees up room
115  * in the string, so it returns the unescaped string in the 
116  * source */
117 gchar *matcher_unescape_str(gchar *str)
118 {
119         gchar *tmp = alloca(strlen(str) + 1);
120         register gchar *src = tmp;
121         register gchar *dst = str;
122         
123         strcpy(tmp, str);
124
125         for ( ; *src; src++) {
126                 if (*src != '\\') 
127                         *dst++ = *src;
128                 else {
129                         src++;
130                         if (*src == '\\')
131                                 *dst++ = '\\';                          /* insert backslash */
132                         else if (*src == 'n')                           /* insert control characters */
133                                 *dst++ = '\n';
134                         else if (*src == 'r') 
135                                 *dst++ = '\r';
136                         else if (*src == 't') 
137                                 *dst++ = '\t';
138                         else if (*src == 'r') 
139                                 *dst++ = '\r';
140                         else if (*src == 'b')
141                                 *dst++ = '\b';
142                         else if (*src == 'f')
143                                 *dst++ = '\f';
144                         else if (*src == '\'' || *src == '\"')          /* insert \' or \" */
145                                 *dst++ = *src;
146                         else {
147                                 /* FIXME: should perhaps escape character... */
148                                 src--;
149                                 *dst++ = *src;
150                         }                               
151                 }
152         }
153         *dst = 0;
154         return str;
155 }
156
157 /* **************** data structure allocation **************** */
158
159
160 MatcherProp * matcherprop_new(gint criteria, gchar * header,
161                               gint matchtype, gchar * expr,
162                               int value)
163 {
164         MatcherProp * prop;
165
166         prop = g_new0(MatcherProp, 1);
167         prop->criteria = criteria;
168         if (header != NULL) {
169                 prop->header       = g_strdup(header);
170                 prop->unesc_header = matcher_unescape_str(g_strdup(header)); 
171         }       
172         else {
173                 prop->header       = NULL;
174                 prop->unesc_header = NULL;
175         }       
176         if (expr != NULL) {
177                 prop->expr       = g_strdup(expr);
178                 prop->unesc_expr = matcher_unescape_str(g_strdup(expr));
179         }       
180         else {
181                 prop->expr       = NULL;
182                 prop->unesc_expr = NULL;
183         }       
184         prop->matchtype = matchtype;
185         prop->preg = NULL;
186         prop->value = value;
187         prop->error = 0;
188
189         return prop;
190 }
191
192 void matcherprop_free(MatcherProp * prop)
193 {
194         if (prop->expr) 
195                 g_free(prop->expr);
196         if (prop->unesc_expr) 
197                 g_free(prop->unesc_expr);
198         if (prop->header)
199                 g_free(prop->header);
200         if (prop->unesc_header) 
201                 g_free(prop->unesc_header);
202         if (prop->preg != NULL) {
203                 regfree(prop->preg);
204                 g_free(prop->preg);
205         }
206         g_free(prop);
207 }
208
209
210 /* ************** match ******************************/
211
212
213 /* match the given string */
214
215 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
216 {
217         gchar * str1;
218         gchar * str2;
219
220         if (str == NULL)
221                 return FALSE;
222
223         switch(prop->matchtype) {
224         case MATCHTYPE_REGEXPCASE:
225         case MATCHTYPE_REGEXP:
226                 if (!prop->preg && (prop->error == 0)) {
227                         prop->preg = g_new0(regex_t, 1);
228                         /* if regexp then don't use the escaped string */
229                         if (regcomp(prop->preg, prop->expr,
230                                     REG_NOSUB | REG_EXTENDED
231                                     | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
232                                     ? REG_ICASE : 0)) != 0) {
233                                 prop->error = 1;
234                                 g_free(prop->preg);
235                         }
236                 }
237                 if (prop->preg == NULL)
238                         return FALSE;
239                 
240                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
241                         return TRUE;
242                 else
243                         return FALSE;
244
245         case MATCHTYPE_MATCH:
246                 return (strstr(str, prop->unesc_expr) != NULL);
247
248         /* FIXME: put upper in unesc_str */
249         case MATCHTYPE_MATCHCASE:
250                 str2 = alloca(strlen(prop->unesc_expr) + 1);
251                 strcpy(str2, prop->unesc_expr);
252                 g_strup(str2);
253                 str1 = alloca(strlen(str) + 1);
254                 strcpy(str1, str);
255                 g_strup(str1);
256                 return (strstr(str1, str2) != NULL);
257                 
258         default:
259                 return FALSE;
260         }
261 }
262
263 gboolean matcherprop_match_execute(MatcherProp * prop, MsgInfo * info)
264 {
265         gchar * file;
266         gchar * cmd;
267
268         file = procmsg_get_message_file(info);
269         if (file == NULL)
270                 return FALSE;
271
272         cmd = matching_build_command(prop->unesc_expr, info);
273         if (cmd == NULL)
274                 return FALSE;
275
276         return (system(cmd) == 0);
277 }
278
279 /* match a message and his headers, hlist can be NULL if you don't
280    want to use headers */
281
282 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
283 {
284         time_t t;
285
286         switch(prop->criteria) {
287         case MATCHCRITERIA_ALL:
288                 return 1;
289         case MATCHCRITERIA_UNREAD:
290                 return MSG_IS_UNREAD(info->flags);
291         case MATCHCRITERIA_NOT_UNREAD:
292                 return !MSG_IS_UNREAD(info->flags);
293         case MATCHCRITERIA_NEW:
294                 return MSG_IS_NEW(info->flags);
295         case MATCHCRITERIA_NOT_NEW:
296                 return !MSG_IS_NEW(info->flags);
297         case MATCHCRITERIA_MARKED:
298                 return MSG_IS_MARKED(info->flags);
299         case MATCHCRITERIA_NOT_MARKED:
300                 return !MSG_IS_MARKED(info->flags);
301         case MATCHCRITERIA_DELETED:
302                 return MSG_IS_DELETED(info->flags);
303         case MATCHCRITERIA_NOT_DELETED:
304                 return !MSG_IS_DELETED(info->flags);
305         case MATCHCRITERIA_REPLIED:
306                 return MSG_IS_REPLIED(info->flags);
307         case MATCHCRITERIA_NOT_REPLIED:
308                 return !MSG_IS_REPLIED(info->flags);
309         case MATCHCRITERIA_FORWARDED:
310                 return MSG_IS_FORWARDED(info->flags);
311         case MATCHCRITERIA_NOT_FORWARDED:
312                 return !MSG_IS_FORWARDED(info->flags);
313         case MATCHCRITERIA_SUBJECT:
314                 return matcherprop_string_match(prop, info->subject);
315         case MATCHCRITERIA_NOT_SUBJECT:
316                 return !matcherprop_string_match(prop, info->subject);
317         case MATCHCRITERIA_FROM:
318                 return matcherprop_string_match(prop, info->from);
319         case MATCHCRITERIA_NOT_FROM:
320                 return !matcherprop_string_match(prop, info->from);
321         case MATCHCRITERIA_TO:
322                 return matcherprop_string_match(prop, info->to);
323         case MATCHCRITERIA_NOT_TO:
324                 return !matcherprop_string_match(prop, info->to);
325         case MATCHCRITERIA_CC:
326                 return matcherprop_string_match(prop, info->cc);
327         case MATCHCRITERIA_NOT_CC:
328                 return !matcherprop_string_match(prop, info->cc);
329         case MATCHCRITERIA_TO_OR_CC:
330                 return matcherprop_string_match(prop, info->to)
331                         || matcherprop_string_match(prop, info->cc);
332         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
333                 return !(matcherprop_string_match(prop, info->to)
334                 || matcherprop_string_match(prop, info->cc));
335         case MATCHCRITERIA_AGE_GREATER:
336                 t = time(NULL);
337                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
338         case MATCHCRITERIA_AGE_LOWER:
339                 t = time(NULL);
340                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
341         case MATCHCRITERIA_SCORE_GREATER:
342                 return info->score >= prop->value;
343         case MATCHCRITERIA_SCORE_LOWER:
344                 return info->score <= prop->value;
345         case MATCHCRITERIA_SCORE_EQUAL:
346                 return info->score == prop->value;
347         case MATCHCRITERIA_NEWSGROUPS:
348                 return matcherprop_string_match(prop, info->newsgroups);
349         case MATCHCRITERIA_NOT_NEWSGROUPS:
350                 return !matcherprop_string_match(prop, info->newsgroups);
351         case MATCHCRITERIA_INREPLYTO:
352                 return matcherprop_string_match(prop, info->inreplyto);
353         case MATCHCRITERIA_NOT_INREPLYTO:
354                 return !matcherprop_string_match(prop, info->inreplyto);
355         case MATCHCRITERIA_REFERENCES:
356                 return matcherprop_string_match(prop, info->references);
357         case MATCHCRITERIA_NOT_REFERENCES:
358                 return !matcherprop_string_match(prop, info->references);
359         case MATCHCRITERIA_EXECUTE:
360                 return matcherprop_match_execute(prop, info);
361         case MATCHCRITERIA_NOT_EXECUTE:
362                 return !matcherprop_match_execute(prop, info);
363         default:
364                 return 0;
365         }
366 }
367
368 /* ********************* MatcherList *************************** */
369
370
371 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
372 {
373         MatcherList * cond;
374
375         cond = g_new0(MatcherList, 1);
376
377         cond->matchers = matchers;
378         cond->bool_and = bool_and;
379
380         return cond;
381 }
382
383 void matcherlist_free(MatcherList * cond)
384 {
385         GSList * l;
386
387         for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
388                 matcherprop_free((MatcherProp *) l->data);
389         }
390         g_free(cond);
391 }
392
393 /*
394   skip the headers
395  */
396
397 static void matcherlist_skip_headers(FILE *fp)
398 {
399         gchar buf[BUFFSIZE];
400
401         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
402         }
403 }
404
405 /*
406   matcherprop_match_one_header
407   returns TRUE if buf matchs the MatchersProp criteria
408  */
409
410 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
411                                              gchar * buf)
412 {
413         gboolean result;
414         Header *header;
415
416         switch(matcher->criteria) {
417         case MATCHCRITERIA_HEADER:
418         case MATCHCRITERIA_NOT_HEADER:
419                 header = procheader_parse_header(buf);
420                 if (!header)
421                         return FALSE;
422                 if (procheader_headername_equal(header->name,
423                                                 matcher->header)) {
424                         if (matcher->criteria == MATCHCRITERIA_HEADER)
425                                 result = matcherprop_string_match(matcher, header->body);
426                         else
427                                 result = !matcherprop_string_match(matcher, header->body);
428                         procheader_header_free(header);
429                         return result;
430                 }
431                 else {
432                         procheader_header_free(header);
433                 }
434                 break;
435         case MATCHCRITERIA_HEADERS_PART:
436         case MATCHCRITERIA_MESSAGE:
437                 return matcherprop_string_match(matcher, buf);
438         case MATCHCRITERIA_NOT_MESSAGE:
439         case MATCHCRITERIA_NOT_HEADERS_PART:
440                 return !matcherprop_string_match(matcher, buf);
441         }
442         return FALSE;
443 }
444
445 /*
446   matcherprop_criteria_header
447   returns TRUE if the headers must be matched
448  */
449
450 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
451 {
452         switch(matcher->criteria) {
453         case MATCHCRITERIA_HEADER:
454         case MATCHCRITERIA_NOT_HEADER:
455         case MATCHCRITERIA_HEADERS_PART:
456         case MATCHCRITERIA_NOT_HEADERS_PART:
457                 return TRUE;
458         default:
459                 return FALSE;
460         }
461 }
462
463 static gboolean matcherprop_criteria_message(MatcherProp * matcher)
464 {
465         switch(matcher->criteria) {
466         case MATCHCRITERIA_MESSAGE:
467         case MATCHCRITERIA_NOT_MESSAGE:
468                 return TRUE;
469         default:
470                 return FALSE;
471         }
472 }
473
474 /*
475   matcherlist_match_one_header
476   returns TRUE if match should stop
477  */
478
479 static gboolean matcherlist_match_one_header(MatcherList * matchers,
480                                          gchar * buf)
481 {
482         GSList * l;
483
484         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
485                 MatcherProp * matcher = (MatcherProp *) l->data;
486
487                 if (matcherprop_criteria_headers(matcher) ||
488                     matcherprop_criteria_message(matcher)) {
489                         if (matcherprop_match_one_header(matcher, buf)) {
490                                 matcher->result = TRUE;
491                         }
492                 }
493
494                 if (matcherprop_criteria_headers(matcher)) {
495                         if (matcher->result) {
496                                 if (!matchers->bool_and)
497                                         return TRUE;
498                         }
499                 }
500         }
501
502         return FALSE;
503 }
504
505 /*
506   matcherlist_match_headers
507   returns TRUE if one of the headers matchs the MatcherList criteria
508  */
509
510 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp)
511 {
512         gchar buf[BUFFSIZE];
513
514         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
515                 if (matcherlist_match_one_header(matchers, buf))
516                         return TRUE;
517
518         return FALSE;
519 }
520
521 /*
522   matcherprop_criteria_body
523   returns TRUE if the body must be matched
524  */
525
526 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
527 {
528         switch(matcher->criteria) {
529         case MATCHCRITERIA_BODY_PART:
530         case MATCHCRITERIA_NOT_BODY_PART:
531                 return TRUE;
532         default:
533                 return FALSE;
534         }
535 }
536
537 /*
538   matcherprop_match_line
539   returns TRUE if the string matchs the MatcherProp criteria
540  */
541
542 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
543 {
544         switch(matcher->criteria) {
545         case MATCHCRITERIA_BODY_PART:
546         case MATCHCRITERIA_MESSAGE:
547                 return matcherprop_string_match(matcher, line);
548         case MATCHCRITERIA_NOT_BODY_PART:
549         case MATCHCRITERIA_NOT_MESSAGE:
550                 return !matcherprop_string_match(matcher, line);
551         }
552         return FALSE;
553 }
554
555 /*
556   matcherlist_match_line
557   returns TRUE if the string matchs the MatcherList criteria
558  */
559
560 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line)
561 {
562         GSList * l;
563
564         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
565                 MatcherProp * matcher = (MatcherProp *) l->data;
566
567                 if (matcherprop_criteria_body(matcher) ||
568                     matcherprop_criteria_message(matcher)) {
569                         if (matcherprop_match_line(matcher, line)) {
570                                 matcher->result = TRUE;
571                         }
572                 }
573                         
574                 if (matcher->result) {
575                         if (!matchers->bool_and)
576                                 return TRUE;
577                 }
578         }
579         return FALSE;
580 }
581
582 /*
583   matcherlist_match_body
584   returns TRUE if one line of the body matchs the MatcherList criteria
585  */
586
587 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp)
588 {
589         gchar buf[BUFFSIZE];
590
591         while (fgets(buf, sizeof(buf), fp) != NULL)
592                 if (matcherlist_match_line(matchers, buf))
593                         return TRUE;
594
595         return FALSE;
596 }
597
598 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
599                                 gboolean result)
600 {
601         gboolean read_headers;
602         gboolean read_body;
603         GSList * l;
604         FILE * fp;
605         gchar * file;
606
607         /* file need to be read ? */
608
609         read_headers = FALSE;
610         read_body = FALSE;
611         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
612                 MatcherProp * matcher = (MatcherProp *) l->data;
613
614                 if (matcherprop_criteria_headers(matcher))
615                         read_headers = TRUE;
616                 if (matcherprop_criteria_body(matcher))
617                         read_body = TRUE;
618                 if (matcherprop_criteria_message(matcher)) {
619                         read_headers = TRUE;
620                         read_body = TRUE;
621                 }
622                 matcher->result = FALSE;
623         }
624
625         if (!read_headers && !read_body)
626                 return result;
627
628         file = procmsg_get_message_file(info);
629         if (file == NULL)
630                 return FALSE;
631
632         if ((fp = fopen(file, "r")) == NULL) {
633                 FILE_OP_ERROR(file, "fopen");
634                 g_free(file);
635                 return result;
636         }
637
638         /* read the headers */
639
640         if (read_headers) {
641                 if (matcherlist_match_headers(matchers, fp))
642                         read_body = FALSE;
643         }
644         else {
645                 matcherlist_skip_headers(fp);
646         }
647
648         /* read the body */
649         if (read_body) {
650                 matcherlist_match_body(matchers, fp);
651         }
652         
653         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
654                 MatcherProp * matcher = (MatcherProp *) l->data;
655
656                 if (matcherprop_criteria_headers(matcher) ||
657                     matcherprop_criteria_body(matcher) ||
658                     matcherprop_criteria_message(matcher))
659                         if (matcher->result) {
660                                 if (!matchers->bool_and) {
661                                         result = TRUE;
662                                         break;
663                                 }
664                         }
665                         else {
666                                 if (matchers->bool_and) {
667                                         result = FALSE;
668                                         break;
669                                 }
670                 }
671         }
672
673         g_free(file);
674
675         fclose(fp);
676         
677         return result;
678 }
679
680 /* test a list of condition */
681
682 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
683 {
684         GSList * l;
685         gboolean result;
686
687         if (matchers->bool_and)
688                 result = TRUE;
689         else
690                 result = FALSE;
691
692         /* test the cached elements */
693
694         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
695                 MatcherProp * matcher = (MatcherProp *) l->data;
696
697                 switch(matcher->criteria) {
698                 case MATCHCRITERIA_ALL:
699                 case MATCHCRITERIA_UNREAD:
700                 case MATCHCRITERIA_NOT_UNREAD:
701                 case MATCHCRITERIA_NEW:
702                 case MATCHCRITERIA_NOT_NEW:
703                 case MATCHCRITERIA_MARKED:
704                 case MATCHCRITERIA_NOT_MARKED:
705                 case MATCHCRITERIA_DELETED:
706                 case MATCHCRITERIA_NOT_DELETED:
707                 case MATCHCRITERIA_REPLIED:
708                 case MATCHCRITERIA_NOT_REPLIED:
709                 case MATCHCRITERIA_FORWARDED:
710                 case MATCHCRITERIA_NOT_FORWARDED:
711                 case MATCHCRITERIA_SUBJECT:
712                 case MATCHCRITERIA_NOT_SUBJECT:
713                 case MATCHCRITERIA_FROM:
714                 case MATCHCRITERIA_NOT_FROM:
715                 case MATCHCRITERIA_TO:
716                 case MATCHCRITERIA_NOT_TO:
717                 case MATCHCRITERIA_CC:
718                 case MATCHCRITERIA_NOT_CC:
719                 case MATCHCRITERIA_TO_OR_CC:
720                 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
721                 case MATCHCRITERIA_AGE_GREATER:
722                 case MATCHCRITERIA_AGE_LOWER:
723                 case MATCHCRITERIA_NEWSGROUPS:
724                 case MATCHCRITERIA_NOT_NEWSGROUPS:
725                 case MATCHCRITERIA_INREPLYTO:
726                 case MATCHCRITERIA_NOT_INREPLYTO:
727                 case MATCHCRITERIA_REFERENCES:
728                 case MATCHCRITERIA_NOT_REFERENCES:
729                 case MATCHCRITERIA_SCORE_GREATER:
730                 case MATCHCRITERIA_SCORE_LOWER:
731                 case MATCHCRITERIA_SCORE_EQUAL:
732                 case MATCHCRITERIA_EXECUTE:
733                 case MATCHCRITERIA_NOT_EXECUTE:
734                         if (matcherprop_match(matcher, info)) {
735                                 if (!matchers->bool_and) {
736                                         return TRUE;
737                                 }
738                         }
739                         else {
740                                 if (matchers->bool_and) {
741                                         return FALSE;
742                                 }
743                         }
744                 }
745         }
746
747         /* test the condition on the file */
748
749         if (matcherlist_match_file(matchers, info, result)) {
750                 if (!matchers->bool_and)
751                         return TRUE;
752         }
753         else {
754                 if (matchers->bool_and)
755                         return FALSE;
756         }
757
758         return result;
759 }
760
761
762 gchar * matcherprop_to_string(MatcherProp * matcher)
763 {
764         gchar * matcher_str = NULL;
765         gchar * criteria_str;
766         gchar * matchtype_str;
767         int i;
768         gchar * p;
769         gint count;
770         gchar * expr_str;
771         gchar * out;
772
773         criteria_str = NULL;
774         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
775             i++) {
776                 if (matchparser_tab[i].id == matcher->criteria)
777                         criteria_str = matchparser_tab[i].str;
778         }
779         if (criteria_str == NULL)
780                 return NULL;
781
782         switch(matcher->criteria) {
783         case MATCHCRITERIA_AGE_GREATER:
784         case MATCHCRITERIA_AGE_LOWER:
785         case MATCHCRITERIA_SCORE_GREATER:
786         case MATCHCRITERIA_SCORE_LOWER:
787         case MATCHCRITERIA_SCORE_EQUAL:
788                 return g_strdup_printf("%s %i", criteria_str, matcher->value);
789                 break;
790         case MATCHCRITERIA_ALL:
791         case MATCHCRITERIA_UNREAD:
792         case MATCHCRITERIA_NOT_UNREAD:
793         case MATCHCRITERIA_NEW:
794         case MATCHCRITERIA_NOT_NEW:
795         case MATCHCRITERIA_MARKED:
796         case MATCHCRITERIA_NOT_MARKED:
797         case MATCHCRITERIA_DELETED:
798         case MATCHCRITERIA_NOT_DELETED:
799         case MATCHCRITERIA_REPLIED:
800         case MATCHCRITERIA_NOT_REPLIED:
801         case MATCHCRITERIA_FORWARDED:
802         case MATCHCRITERIA_NOT_FORWARDED:
803                 return g_strdup(criteria_str);
804         }
805
806         matchtype_str = NULL;
807         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
808             i++) {
809                 if (matchparser_tab[i].id == matcher->matchtype)
810                         matchtype_str = matchparser_tab[i].str;
811         }
812
813         if (matchtype_str == NULL)
814                 return NULL;
815
816         switch (matcher->matchtype) {
817         case MATCHTYPE_MATCH:
818         case MATCHTYPE_MATCHCASE:
819         case MATCHTYPE_REGEXP:
820         case MATCHTYPE_REGEXPCASE:
821                 if (matcher->header)
822                         matcher_str =
823                                 g_strdup_printf("%s \"%s\" %s \"%s\"",
824                                            criteria_str, matcher->header,
825                                            matchtype_str, matcher->expr);
826                 else
827                         matcher_str =
828                                 g_strdup_printf("%s %s \"%s\"", criteria_str,
829                                                 matchtype_str, matcher->expr);
830                 break;
831         }
832
833         return matcher_str;
834 }
835
836 gchar * matcherlist_to_string(MatcherList * matchers)
837 {
838         gint count;
839         gchar ** vstr;
840         GSList * l;
841         gchar ** cur_str;
842         gchar * result;
843
844         count = g_slist_length(matchers->matchers);
845         vstr = g_new(gchar *, count + 1);
846
847         for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
848              l = g_slist_next(l), cur_str ++) {
849                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
850                 if (*cur_str == NULL)
851                         break;
852         }
853         *cur_str = NULL;
854         
855         if (matchers->bool_and)
856                 result = g_strjoinv(" & ", vstr);
857         else
858                 result = g_strjoinv(" | ", vstr);
859
860         for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)
861                 g_free(*cur_str);
862         g_free(vstr);
863
864         return result;
865 }
866
867 /* matching_build_command() - preferably cmd should be unescaped */
868 gchar * matching_build_command(gchar * cmd, MsgInfo * info)
869 {
870         gchar * s = cmd;
871         gchar * filename = NULL;
872         gchar * processed_cmd;
873         gchar * p;
874         gint size;
875
876         size = strlen(cmd) + 1;
877         while (*s != '\0') {
878                 if (*s == '%') {
879                         s++;
880                         switch (*s) {
881                         case '%':
882                                 size -= 1;
883                                 break;
884                         case 's': /* subject */
885                                 size += strlen(info->subject) - 2;
886                                 break;
887                         case 'f': /* from */
888                                 size += strlen(info->from) - 2;
889                                 break;
890                         case 't': /* to */
891                                 size += strlen(info->to) - 2;
892                                 break;
893                         case 'c': /* cc */
894                                 size += strlen(info->cc) - 2;
895                                 break;
896                         case 'd': /* date */
897                                 size += strlen(info->date) - 2;
898                                 break;
899                         case 'i': /* message-id */
900                                 size += strlen(info->msgid) - 2;
901                                 break;
902                         case 'n': /* newsgroups */
903                                 size += strlen(info->newsgroups) - 2;
904                                 break;
905                         case 'r': /* references */
906                                 size += strlen(info->references) - 2;
907                                 break;
908                         case 'F': /* file */
909                                 filename = folder_item_fetch_msg(info->folder,
910                                                                  info->msgnum);
911                                 
912                                 if (filename == NULL) {
913                                         g_warning(_("filename is not set"));
914                                         return NULL;
915                                 }
916                                 else
917                                         size += strlen(filename) - 2;
918                                 break;
919                         }
920                         s++;
921                 }
922                 else s++;
923         }
924
925
926         processed_cmd = g_new0(gchar, size);
927         s = cmd;
928         p = processed_cmd;
929
930         while (*s != '\0') {
931                 if (*s == '%') {
932                         s++;
933                         switch (*s) {
934                         case '%':
935                                 *p = '%';
936                                 p++;
937                                 break;
938                         case 's': /* subject */
939                                 if (info->subject != NULL)
940                                         strcpy(p, info->subject);
941                                 else
942                                         strcpy(p, _("(none)"));
943                                 p += strlen(p);
944                                 break;
945                         case 'f': /* from */
946                                 if (info->from != NULL)
947                                         strcpy(p, info->from);
948                                 else
949                                         strcpy(p, _("(none)"));
950                                 p += strlen(p);
951                                 break;
952                         case 't': /* to */
953                                 if (info->to != NULL)
954                                         strcpy(p, info->to);
955                                 else
956                                         strcpy(p, _("(none)"));
957                                 p += strlen(p);
958                                 break;
959                         case 'c': /* cc */
960                                 if (info->cc != NULL)
961                                         strcpy(p, info->cc);
962                                 else
963                                         strcpy(p, _("(none)"));
964                                 p += strlen(p);
965                                 break;
966                         case 'd': /* date */
967                                 if (info->date != NULL)
968                                         strcpy(p, info->date);
969                                 else
970                                         strcpy(p, _("(none)"));
971                                 p += strlen(p);
972                                 break;
973                         case 'i': /* message-id */
974                                 if (info->msgid != NULL)
975                                         strcpy(p, info->msgid);
976                                 else
977                                         strcpy(p, _("(none)"));
978                                 p += strlen(p);
979                                 break;
980                         case 'n': /* newsgroups */
981                                 if (info->newsgroups != NULL)
982                                         strcpy(p, info->newsgroups);
983                                 else
984                                         strcpy(p, _("(none)"));
985                                 p += strlen(p);
986                                 break;
987                         case 'r': /* references */
988                                 if (info->references != NULL)
989                                         strcpy(p, info->references);
990                                 else
991                                         strcpy(p, _("(none)"));
992                                 p += strlen(p);
993                                 break;
994                         case 'F': /* file */
995                                 strcpy(p, filename);
996                                 p += strlen(p);
997                                 break;
998                         default:
999                                 *p = '%';
1000                                 p++;
1001                                 *p = *s;
1002                                 p++;
1003                                 break;
1004                         }
1005                         s++;
1006                 }
1007                 else {
1008                         *p = *s;
1009                         p++;
1010                         s++;
1011                 }
1012         }
1013
1014         debug_print("*** exec string \"%s\"\n", processed_cmd);
1015         return processed_cmd;
1016 }
1017
1018
1019 /* ************************************************************ */
1020 /* ************************************************************ */
1021 /* ************************************************************ */
1022 /* ************************************************************ */
1023 /* ************************************************************ */
1024
1025
1026 static void prefs_scoring_write(FILE * fp, GSList * prefs_scoring)
1027 {
1028         GSList * cur;
1029
1030         for (cur = prefs_scoring; cur != NULL; cur = cur->next) {
1031                 gchar *scoring_str;
1032                 ScoringProp * prop;
1033
1034                 prop = (ScoringProp *) cur->data;
1035                 scoring_str = scoringprop_to_string(prop);
1036                 if (fputs(scoring_str, fp) == EOF ||
1037                     fputc('\n', fp) == EOF) {
1038                         FILE_OP_ERROR("scoring config", "fputs || fputc");
1039                         g_free(scoring_str);
1040                         return;
1041                 }
1042                 g_free(scoring_str);
1043         }
1044 }
1045
1046 static void prefs_filtering_write(FILE * fp, GSList * prefs_scoring)
1047 {
1048         GSList * cur;
1049
1050         for (cur = prefs_scoring; cur != NULL; cur = cur->next) {
1051                 gchar *filtering_str;
1052                 FilteringProp * prop;
1053
1054                 prop = (FilteringProp *) cur->data;
1055                 filtering_str = filteringprop_to_string(prop);
1056                 
1057                 if (fputs(filtering_str, fp) == EOF ||
1058                     fputc('\n', fp) == EOF) {
1059                         FILE_OP_ERROR("filtering config", "fputs || fputc");
1060                         g_free(filtering_str);
1061                         return;
1062                 }
1063                 g_free(filtering_str);
1064         }
1065 }
1066
1067 static gboolean prefs_matcher_write_func(GNode *node, gpointer data)
1068 {
1069         FolderItem *item;
1070         FILE * fp = data;
1071         gchar * id;
1072         GSList * prefs_scoring;
1073         GSList * prefs_filtering;
1074
1075         if (node != NULL) {
1076                 item = node->data;
1077                 /* prevent from the warning */
1078                 if (item->path == NULL)
1079                         return FALSE;
1080                 id = folder_item_get_identifier(item);
1081                 if (id == NULL)
1082                         return FALSE;
1083                 prefs_scoring = item->prefs->scoring;
1084                 prefs_filtering = item->prefs->processing;
1085         }
1086         else {
1087                 item = NULL;
1088                 id = g_strdup("global"); /* because it is g_freed */
1089                 prefs_scoring = global_scoring;
1090                 prefs_filtering = global_processing;
1091         }
1092
1093         if (prefs_filtering != NULL || prefs_scoring != NULL) {
1094                 fprintf(fp, "[%s]\n", id);
1095
1096                 prefs_filtering_write(fp, prefs_filtering);
1097                 prefs_scoring_write(fp, prefs_scoring);
1098
1099                 fputc('\n', fp);
1100         }
1101
1102         g_free(id);
1103
1104         return FALSE;
1105 }
1106
1107 static void prefs_matcher_save(FILE * fp)
1108 {
1109         GList * cur;
1110
1111         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1112                 Folder *folder;
1113
1114                 folder = (Folder *) cur->data;
1115                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1116                                 prefs_matcher_write_func, fp);
1117         }
1118         prefs_matcher_write_func(NULL, fp);
1119 }
1120
1121
1122 void prefs_matcher_write_config(void)
1123 {
1124         gchar *rcpath;
1125         PrefFile *pfile;
1126         GSList *cur;
1127         ScoringProp * prop;
1128
1129         debug_print(_("Writing matcher configuration...\n"));
1130
1131         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1132                              MATCHER_RC, NULL);
1133
1134         if ((pfile = prefs_write_open(rcpath)) == NULL) {
1135                 g_warning(_("failed to write configuration to file\n"));
1136                 g_free(rcpath);
1137                 return;
1138         }
1139
1140
1141         prefs_matcher_save(pfile->fp);
1142
1143         g_free(rcpath);
1144
1145         if (prefs_write_close(pfile) < 0) {
1146                 g_warning(_("failed to write configuration to file\n"));
1147                 return;
1148         }
1149 }
1150
1151
1152
1153
1154
1155
1156 /* ******************************************************************* */
1157
1158 void prefs_matcher_read_config(void)
1159 {
1160         gchar * rcpath;
1161         FILE * f;
1162
1163         prefs_scoring_clear();
1164         prefs_filtering_clear();
1165
1166         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
1167         f = fopen(rcpath, "r");
1168         g_free(rcpath);
1169
1170         if (f != NULL)
1171                 matcher_parser_start_parsing(f);
1172         else {
1173                 /* previous version compatibily */
1174
1175                 printf("reading filtering\n");
1176                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1177                                      FILTERING_RC, NULL);
1178                 f = fopen(rcpath, "r");
1179                 g_free(rcpath);
1180                 
1181                 if (f != NULL) {
1182                         matcher_parser_start_parsing(f);
1183                         fclose(matcher_parserin);
1184                 }
1185                 
1186                 printf("reading scoring\n");
1187                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1188                                      SCORING_RC, NULL);
1189                 f = fopen(rcpath, "r");
1190                 g_free(rcpath);
1191                 
1192                 if (f != NULL) {
1193                         matcher_parser_start_parsing(f);
1194                         fclose(matcher_parserin);
1195                 }
1196         }
1197 }