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