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