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