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