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