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