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