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