initial message templates support
[claws.git] / src / matcher.c
blob9a671c52aedc520fe83cff7865b846e9d8a3a492
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2001 Hiroyuki Yamamoto & The Sylpheed Claws Team
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.
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.
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.
20 /*
21 * initial Hoa initial
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.
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"
38 struct _MatchParser {
39 gint id;
40 gchar * str;
43 typedef struct _MatchParser MatchParser;
45 static MatchParser matchparser_tab[] = {
46 /* msginfo flags */
47 {MATCHING_ALL, "all"},
48 {MATCHING_UNREAD, "unread"},
49 {MATCHING_NOT_UNREAD, "~unread"},
50 {MATCHING_NEW, "new"},
51 {MATCHING_NOT_NEW, "~new"},
52 {MATCHING_MARKED, "marked"},
53 {MATCHING_NOT_MARKED, "~marked"},
54 {MATCHING_DELETED, "deleted"},
55 {MATCHING_NOT_DELETED, "~deleted"},
56 {MATCHING_REPLIED, "replied"},
57 {MATCHING_NOT_REPLIED, "~replied"},
58 {MATCHING_FORWARDED, "forwarded"},
59 {MATCHING_NOT_FORWARDED, "~forwarded"},
61 /* msginfo headers */
62 {MATCHING_SUBJECT, "subject"},
63 {MATCHING_NOT_SUBJECT, "~subject"},
64 {MATCHING_FROM, "from"},
65 {MATCHING_NOT_FROM, "~from"},
66 {MATCHING_TO, "to"},
67 {MATCHING_NOT_TO, "~to"},
68 {MATCHING_CC, "cc"},
69 {MATCHING_NOT_CC, "~cc"},
70 {MATCHING_TO_OR_CC, "to_or_cc"},
71 {MATCHING_NOT_TO_AND_NOT_CC, "~to_or_cc"},
72 {MATCHING_AGE_GREATER, "age_greater"},
73 {MATCHING_AGE_LOWER, "age_lower"},
74 {MATCHING_NEWSGROUPS, "newsgroups"},
75 {MATCHING_NOT_NEWSGROUPS, "~newsgroups"},
76 {MATCHING_INREPLYTO, "inreplyto"},
77 {MATCHING_NOT_INREPLYTO, "~inreplyto"},
78 {MATCHING_REFERENCES, "references"},
79 {MATCHING_NOT_REFERENCES, "~references"},
80 {MATCHING_SCORE_GREATER, "score_greater"},
81 {MATCHING_SCORE_LOWER, "score_lower"},
83 /* content have to be read */
84 {MATCHING_HEADER, "header"},
85 {MATCHING_NOT_HEADER, "~header"},
86 {MATCHING_HEADERS_PART, "headers_part"},
87 {MATCHING_NOT_HEADERS_PART, "~headers_part"},
88 {MATCHING_MESSAGE, "message"},
89 {MATCHING_NOT_MESSAGE, "~message"},
90 {MATCHING_BODY_PART, "body_part"},
91 {MATCHING_NOT_BODY_PART, "~body_part"},
92 {MATCHING_EXECUTE, "execute"},
93 {MATCHING_NOT_EXECUTE, "~execute"},
95 /* match type */
96 {MATCHING_MATCHCASE, "matchcase"},
97 {MATCHING_MATCH, "match"},
98 {MATCHING_REGEXPCASE, "regexpcase"},
99 {MATCHING_REGEXP, "regexp"},
101 /* actions */
102 {MATCHING_SCORE, "score"},
104 /* actions */
105 {MATCHING_ACTION_MOVE, "move"},
106 {MATCHING_ACTION_COPY, "copy"},
107 {MATCHING_ACTION_DELETE, "delete"},
108 {MATCHING_ACTION_MARK, "mark"},
109 {MATCHING_ACTION_UNMARK, "unmark"},
110 {MATCHING_ACTION_MARK_AS_READ, "mark_as_read"},
111 {MATCHING_ACTION_MARK_AS_UNREAD, "mark_as_unread"},
112 {MATCHING_ACTION_FORWARD, "forward"},
113 {MATCHING_ACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
114 {MATCHING_ACTION_COLOR, "color"}
115 /* {MATCHING_EXECUTE, "execute"}, */
118 gchar * get_matchparser_tab_str(gint id)
120 gint i;
122 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
123 i++) {
124 if (matchparser_tab[i].id == id)
125 return matchparser_tab[i].str;
127 return NULL;
133 syntax for matcher
135 header "x-mailing" match "toto"
136 subject match "regexp" & to regexp "regexp"
137 subject match "regexp" | to regexpcase "regexp" | age_sup 5
140 static gboolean matcher_is_blank(gchar ch);
142 /* ******************* parser *********************** */
144 static gboolean matcher_is_blank(gchar ch)
146 return (ch == ' ') || (ch == '\t');
149 /* parse for one condition */
151 MatcherProp * matcherprop_parse(gchar ** str)
153 MatcherProp * prop;
154 gchar * tmp;
155 gint key;
156 gint value;
157 gchar * expr;
158 gint match;
159 gchar * header = NULL;
161 tmp = * str;
162 key = matcher_parse_keyword(&tmp);
163 if (tmp == NULL) {
164 * str = NULL;
165 return NULL;
168 switch (key) {
169 case MATCHING_AGE_LOWER:
170 case MATCHING_AGE_GREATER:
171 case MATCHING_SCORE_LOWER:
172 case MATCHING_SCORE_GREATER:
173 value = matcher_parse_number(&tmp);
174 if (tmp == NULL) {
175 * str = NULL;
176 return NULL;
178 *str = tmp;
180 prop = matcherprop_new(key, NULL, 0, NULL, value);
182 return prop;
184 case MATCHING_ALL:
185 case MATCHING_UNREAD:
186 case MATCHING_NOT_UNREAD:
187 case MATCHING_NEW:
188 case MATCHING_NOT_NEW:
189 case MATCHING_MARKED:
190 case MATCHING_NOT_MARKED:
191 case MATCHING_DELETED:
192 case MATCHING_NOT_DELETED:
193 case MATCHING_REPLIED:
194 case MATCHING_NOT_REPLIED:
195 case MATCHING_FORWARDED:
196 case MATCHING_NOT_FORWARDED:
197 prop = matcherprop_new(key, NULL, 0, NULL, 0);
198 *str = tmp;
200 return prop;
202 case MATCHING_SUBJECT:
203 case MATCHING_NOT_SUBJECT:
204 case MATCHING_FROM:
205 case MATCHING_NOT_FROM:
206 case MATCHING_TO:
207 case MATCHING_NOT_TO:
208 case MATCHING_CC:
209 case MATCHING_NOT_CC:
210 case MATCHING_TO_OR_CC:
211 case MATCHING_NOT_TO_AND_NOT_CC:
212 case MATCHING_NEWSGROUPS:
213 case MATCHING_NOT_NEWSGROUPS:
214 case MATCHING_INREPLYTO:
215 case MATCHING_NOT_REFERENCES:
216 case MATCHING_REFERENCES:
217 case MATCHING_NOT_INREPLYTO:
218 case MATCHING_MESSAGE:
219 case MATCHING_NOT_MESSAGE:
220 case MATCHING_EXECUTE:
221 case MATCHING_NOT_EXECUTE:
222 case MATCHING_HEADERS_PART:
223 case MATCHING_NOT_HEADERS_PART:
224 case MATCHING_BODY_PART:
225 case MATCHING_NOT_BODY_PART:
226 case MATCHING_HEADER:
227 case MATCHING_NOT_HEADER:
228 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
229 header = matcher_parse_str(&tmp);
230 if (tmp == NULL) {
231 * str = NULL;
232 return NULL;
236 match = matcher_parse_keyword(&tmp);
237 if (tmp == NULL) {
238 if (header)
239 g_free(header);
240 * str = NULL;
241 return NULL;
244 switch(match) {
245 case MATCHING_REGEXP:
246 case MATCHING_REGEXPCASE:
247 expr = matcher_parse_regexp(&tmp);
248 if (tmp == NULL) {
249 if (header)
250 g_free(header);
251 * str = NULL;
252 return NULL;
254 *str = tmp;
255 prop = matcherprop_new(key, header, match, expr, 0);
256 g_free(expr);
258 return prop;
259 case MATCHING_MATCH:
260 case MATCHING_MATCHCASE:
261 expr = matcher_parse_str(&tmp);
262 if (tmp == NULL) {
263 if (header)
264 g_free(header);
265 * str = NULL;
266 return NULL;
268 *str = tmp;
269 prop = matcherprop_new(key, header, match, expr, 0);
270 g_free(expr);
272 return prop;
273 default:
274 if (header)
275 g_free(header);
276 * str = NULL;
277 return NULL;
279 default:
280 * str = NULL;
281 return NULL;
285 gint matcher_parse_keyword(gchar ** str)
287 gchar * p;
288 gchar * dup;
289 gchar * start;
290 gint i;
291 gint match;
293 dup = alloca(strlen(* str) + 1);
294 p = dup;
295 strcpy(dup, * str);
297 while (matcher_is_blank(*p))
298 p++;
300 start = p;
302 while (!matcher_is_blank(*p) && (*p != '\0'))
303 p++;
305 match = -1;
306 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
307 i++) {
308 if ((strlen(matchparser_tab[i].str) == p - start) &&
309 (strncasecmp(matchparser_tab[i].str, start,
310 p - start) == 0)) {
311 match = i;
312 break;
316 if (match == -1) {
317 * str = NULL;
318 return 0;
321 *p = '\0';
323 *str += p - dup + 1;
324 return matchparser_tab[match].id;
327 gint matcher_parse_number(gchar ** str)
329 gchar * p;
330 gchar * dup;
331 gchar * start;
333 dup = alloca(strlen(* str) + 1);
334 p = dup;
335 strcpy(dup, * str);
337 while (matcher_is_blank(*p))
338 p++;
340 start = p;
342 if (!isdigit(*p) && *p != '-' && *p != '+') {
343 *str = NULL;
344 return 0;
346 if (*p == '-' || *p == '+')
347 p++;
348 while (isdigit(*p))
349 p++;
351 *p = '\0';
353 *str += p - dup + 1;
354 return atoi(start);
357 gboolean matcher_parse_boolean_op(gchar ** str)
359 gchar * p;
361 p = * str;
363 while (matcher_is_blank(*p))
364 p++;
366 if (*p == '|') {
367 *str += p - * str + 1;
368 return FALSE;
370 else if (*p == '&') {
371 *str += p - * str + 1;
372 return TRUE;
374 else {
375 *str = NULL;
376 return FALSE;
380 gchar * matcher_parse_regexp(gchar ** str)
382 gchar * p;
383 gchar * dup;
384 gchar * start;
386 dup = alloca(strlen(* str) + 1);
387 p = dup;
388 strcpy(dup, * str);
390 while (matcher_is_blank(*p))
391 p++;
393 if (*p != '/') {
394 * str = NULL;
395 return NULL;
398 p ++;
399 start = p;
400 while (*p != '/') {
401 if (*p == '\\')
402 p++;
403 p++;
405 *p = '\0';
407 *str += p - dup + 2;
408 return g_strdup(start);
411 /* matcher_parse_str() - parses a string until it hits a
412 * terminating \". to unescape characters use \. The string
413 * should not be used directly: matcher_unescape_str()
414 * returns a string that can be used directly. */
415 gchar * matcher_parse_str(gchar ** str)
417 gchar * p;
418 gchar * dup;
419 gchar * start;
420 gchar * dest;
422 dup = alloca(strlen(* str) + 1);
423 p = dup;
424 strcpy(dup, * str);
426 while (matcher_is_blank(*p))
427 p++;
429 if (*p != '"') {
430 * str = NULL;
431 return NULL;
434 p ++;
435 start = p;
436 dest = p;
438 for ( ; *p && *p != '\"'; p++, dest++) {
439 if (*p == '\\') {
440 *dest++ = *p++;
441 *dest = *p;
443 else
444 *dest = *p;
446 *dest = '\0';
448 *str += dest - dup + 2;
449 return g_strdup(start);
452 gchar *matcher_unescape_str(gchar *str)
454 gchar *tmp = alloca(strlen(str) + 1);
455 register gchar *src = tmp;
456 register gchar *dst = str;
458 strcpy(tmp, str);
460 for ( ; *src; src++) {
461 if (*src != '\\')
462 *dst++ = *src;
463 else {
464 src++;
465 if (*src == '\\')
466 *dst++ = '\\';
467 else if (*src == 'n')
468 *dst++ = '\n';
469 else if (*src == 'r')
470 *dst++ = '\r';
471 else if (*src == '\'' || *src == '\"')
472 *dst++ = *src;
473 else {
474 src--;
475 *dst++ = *src;
479 *dst = 0;
480 return str;
483 /* **************** data structure allocation **************** */
486 MatcherProp * matcherprop_new(gint criteria, gchar * header,
487 gint matchtype, gchar * expr,
488 int value)
490 MatcherProp * prop;
492 prop = g_new0(MatcherProp, 1);
493 prop->criteria = criteria;
494 if (header != NULL)
495 prop->header = g_strdup(header);
496 else
497 prop->header = NULL;
498 if (expr != NULL)
499 prop->expr = g_strdup(expr);
500 else
501 prop->expr = NULL;
502 prop->matchtype = matchtype;
503 prop->preg = NULL;
504 prop->value = value;
505 prop->error = 0;
507 return prop;
510 void matcherprop_free(MatcherProp * prop)
512 g_free(prop->expr);
513 if (prop->preg != NULL) {
514 regfree(prop->preg);
515 g_free(prop->preg);
517 g_free(prop);
521 /* ************** match ******************************/
524 /* match the given string */
526 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
528 gchar * str1;
529 gchar * str2;
531 if (str == NULL)
532 return FALSE;
534 switch(prop->matchtype) {
535 case MATCHING_REGEXPCASE:
536 case MATCHING_REGEXP:
537 if (!prop->preg && (prop->error == 0)) {
538 prop->preg = g_new0(regex_t, 1);
539 if (regcomp(prop->preg, prop->expr,
540 REG_NOSUB | REG_EXTENDED
541 | ((prop->matchtype == MATCHING_REGEXPCASE)
542 ? REG_ICASE : 0)) != 0) {
543 prop->error = 1;
544 g_free(prop->preg);
547 if (prop->preg == NULL)
548 return FALSE;
550 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
551 return TRUE;
552 else
553 return FALSE;
555 case MATCHING_MATCH:
556 return (strstr(str, prop->expr) != NULL);
558 case MATCHING_MATCHCASE:
559 str2 = alloca(strlen(prop->expr) + 1);
560 strcpy(str2, prop->expr);
561 g_strup(str2);
562 str1 = alloca(strlen(str) + 1);
563 strcpy(str1, str);
564 g_strup(str1);
565 return (strstr(str1, str2) != NULL);
567 default:
568 return FALSE;
572 gboolean matcherprop_match_execute(MatcherProp * prop, MsgInfo * info)
574 gchar * cmd;
576 cmd = matching_build_command(prop->expr, info);
577 if (cmd == NULL)
578 return FALSE;
580 return (system(cmd) == 0);
583 /* match a message and his headers, hlist can be NULL if you don't
584 want to use headers */
586 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
588 time_t t;
590 switch(prop->criteria) {
591 case MATCHING_ALL:
592 return 1;
593 case MATCHING_UNREAD:
594 return MSG_IS_UNREAD(info->flags);
595 case MATCHING_NOT_UNREAD:
596 return !MSG_IS_UNREAD(info->flags);
597 case MATCHING_NEW:
598 return MSG_IS_NEW(info->flags);
599 case MATCHING_NOT_NEW:
600 return !MSG_IS_NEW(info->flags);
601 case MATCHING_MARKED:
602 return MSG_IS_MARKED(info->flags);
603 case MATCHING_NOT_MARKED:
604 return !MSG_IS_MARKED(info->flags);
605 case MATCHING_DELETED:
606 return MSG_IS_DELETED(info->flags);
607 case MATCHING_NOT_DELETED:
608 return !MSG_IS_DELETED(info->flags);
609 case MATCHING_REPLIED:
610 return MSG_IS_REPLIED(info->flags);
611 case MATCHING_NOT_REPLIED:
612 return !MSG_IS_REPLIED(info->flags);
613 case MATCHING_FORWARDED:
614 return MSG_IS_FORWARDED(info->flags);
615 case MATCHING_NOT_FORWARDED:
616 return !MSG_IS_FORWARDED(info->flags);
617 case MATCHING_SUBJECT:
618 return matcherprop_string_match(prop, info->subject);
619 case MATCHING_NOT_SUBJECT:
620 return !matcherprop_string_match(prop, info->subject);
621 case MATCHING_FROM:
622 return matcherprop_string_match(prop, info->from);
623 case MATCHING_NOT_FROM:
624 return !matcherprop_string_match(prop, info->from);
625 case MATCHING_TO:
626 return matcherprop_string_match(prop, info->to);
627 case MATCHING_NOT_TO:
628 return !matcherprop_string_match(prop, info->to);
629 case MATCHING_CC:
630 return matcherprop_string_match(prop, info->cc);
631 case MATCHING_NOT_CC:
632 return !matcherprop_string_match(prop, info->cc);
633 case MATCHING_TO_OR_CC:
634 return matcherprop_string_match(prop, info->to)
635 || matcherprop_string_match(prop, info->cc);
636 case MATCHING_NOT_TO_AND_NOT_CC:
637 return !(matcherprop_string_match(prop, info->to)
638 || matcherprop_string_match(prop, info->cc));
639 case MATCHING_AGE_GREATER:
640 t = time(NULL);
641 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
642 case MATCHING_AGE_LOWER:
643 t = time(NULL);
644 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
645 case MATCHING_SCORE_GREATER:
646 return info->score >= prop->value;
647 case MATCHING_SCORE_LOWER:
648 return info->score <= prop->value;
649 case MATCHING_NEWSGROUPS:
650 return matcherprop_string_match(prop, info->newsgroups);
651 case MATCHING_NOT_NEWSGROUPS:
652 return !matcherprop_string_match(prop, info->newsgroups);
653 case MATCHING_INREPLYTO:
654 return matcherprop_string_match(prop, info->inreplyto);
655 case MATCHING_NOT_INREPLYTO:
656 return !matcherprop_string_match(prop, info->inreplyto);
657 case MATCHING_REFERENCES:
658 return matcherprop_string_match(prop, info->references);
659 case MATCHING_NOT_REFERENCES:
660 return !matcherprop_string_match(prop, info->references);
661 case MATCHING_EXECUTE:
662 return matcherprop_match_execute(prop, info);
663 case MATCHING_NOT_EXECUTE:
664 return !matcherprop_match_execute(prop, info);
665 default:
666 return 0;
670 /* ********************* MatcherList *************************** */
673 /* parse for a list of conditions */
675 MatcherList * matcherlist_parse(gchar ** str)
677 gchar * tmp;
678 MatcherProp * matcher;
679 GSList * matchers_list = NULL;
680 gboolean bool_and = TRUE;
681 gchar * save;
682 MatcherList * cond;
683 gboolean main_bool_and = TRUE;
684 GSList * l;
686 tmp = * str;
688 matcher = matcherprop_parse(&tmp);
690 if (tmp == NULL) {
691 * str = NULL;
692 return NULL;
694 matchers_list = g_slist_append(matchers_list, matcher);
695 while (matcher) {
696 save = tmp;
697 bool_and = matcher_parse_boolean_op(&tmp);
698 if (tmp == NULL) {
699 tmp = save;
700 matcher = NULL;
702 else {
703 main_bool_and = bool_and;
704 matcher = matcherprop_parse(&tmp);
705 if (tmp != NULL) {
706 matchers_list =
707 g_slist_append(matchers_list, matcher);
709 else {
710 for(l = matchers_list ; l != NULL ;
711 l = g_slist_next(l))
712 matcherprop_free((MatcherProp *)
713 l->data);
714 g_slist_free(matchers_list);
715 * str = NULL;
716 return NULL;
721 cond = matcherlist_new(matchers_list, main_bool_and);
723 * str = tmp;
725 return cond;
728 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
730 MatcherList * cond;
732 cond = g_new0(MatcherList, 1);
734 cond->matchers = matchers;
735 cond->bool_and = bool_and;
737 return cond;
740 void matcherlist_free(MatcherList * cond)
742 GSList * l;
744 for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
745 matcherprop_free((MatcherProp *) l->data);
747 g_free(cond);
751 skip the headers
754 static void matcherlist_skip_headers(FILE *fp)
756 gchar buf[BUFFSIZE];
758 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
763 matcherprop_match_one_header
764 returns TRUE if buf matchs the MatchersProp criteria
767 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
768 gchar * buf)
770 gboolean result;
771 Header *header;
773 switch(matcher->criteria) {
774 case MATCHING_HEADER:
775 case MATCHING_NOT_HEADER:
776 header = procheader_parse_header(buf);
777 if (!header)
778 return FALSE;
779 if (procheader_headername_equal(header->name,
780 matcher->header)) {
781 if (matcher->criteria == MATCHING_HEADER)
782 result = matcherprop_string_match(matcher, header->body);
783 else
784 result = !matcherprop_string_match(matcher, header->body);
785 procheader_header_free(header);
786 return result;
788 else {
789 procheader_header_free(header);
791 break;
792 case MATCHING_HEADERS_PART:
793 case MATCHING_MESSAGE:
794 return matcherprop_string_match(matcher, buf);
795 case MATCHING_NOT_MESSAGE:
796 case MATCHING_NOT_HEADERS_PART:
797 return !matcherprop_string_match(matcher, buf);
799 return FALSE;
803 matcherprop_criteria_header
804 returns TRUE if the headers must be matched
807 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
809 switch(matcher->criteria) {
810 case MATCHING_HEADER:
811 case MATCHING_NOT_HEADER:
812 case MATCHING_HEADERS_PART:
813 case MATCHING_NOT_HEADERS_PART:
814 return TRUE;
815 default:
816 return FALSE;
820 static gboolean matcherprop_criteria_message(MatcherProp * matcher)
822 switch(matcher->criteria) {
823 case MATCHING_MESSAGE:
824 case MATCHING_NOT_MESSAGE:
825 return TRUE;
826 default:
827 return FALSE;
832 matcherlist_match_one_header
833 returns TRUE if match should stop
836 static gboolean matcherlist_match_one_header(MatcherList * matchers,
837 gchar * buf)
839 GSList * l;
841 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
842 MatcherProp * matcher = (MatcherProp *) l->data;
844 if (matcherprop_criteria_headers(matcher) ||
845 matcherprop_criteria_message(matcher)) {
846 if (matcherprop_match_one_header(matcher, buf)) {
847 matcher->result = TRUE;
851 if (matcherprop_criteria_headers(matcher)) {
852 if (matcher->result) {
853 if (!matchers->bool_and)
854 return TRUE;
859 return FALSE;
863 matcherlist_match_headers
864 returns TRUE if one of the headers matchs the MatcherList criteria
867 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp)
869 gchar buf[BUFFSIZE];
871 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
872 if (matcherlist_match_one_header(matchers, buf))
873 return TRUE;
875 return FALSE;
879 matcherprop_criteria_body
880 returns TRUE if the body must be matched
883 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
885 switch(matcher->criteria) {
886 case MATCHING_BODY_PART:
887 case MATCHING_NOT_BODY_PART:
888 return TRUE;
889 default:
890 return FALSE;
895 matcherprop_match_line
896 returns TRUE if the string matchs the MatcherProp criteria
899 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
901 switch(matcher->criteria) {
902 case MATCHING_BODY_PART:
903 case MATCHING_MESSAGE:
904 return matcherprop_string_match(matcher, line);
905 case MATCHING_NOT_BODY_PART:
906 case MATCHING_NOT_MESSAGE:
907 return !matcherprop_string_match(matcher, line);
909 return FALSE;
913 matcherlist_match_line
914 returns TRUE if the string matchs the MatcherList criteria
917 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line)
919 GSList * l;
921 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
922 MatcherProp * matcher = (MatcherProp *) l->data;
924 if (matcherprop_criteria_body(matcher) ||
925 matcherprop_criteria_message(matcher)) {
926 if (matcherprop_match_line(matcher, line)) {
927 matcher->result = TRUE;
931 if (matcher->result) {
932 if (!matchers->bool_and)
933 return TRUE;
936 return FALSE;
940 matcherlist_match_body
941 returns TRUE if one line of the body matchs the MatcherList criteria
944 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp)
946 gchar buf[BUFFSIZE];
948 while (fgets(buf, sizeof(buf), fp) != NULL)
949 if (matcherlist_match_line(matchers, buf))
950 return TRUE;
952 return FALSE;
955 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
956 gboolean result)
958 gboolean read_headers;
959 gboolean read_body;
960 GSList * l;
961 FILE * fp;
962 gchar * file;
963 gboolean is_incorporating = MSG_IS_FILTERING(info->flags);
965 /* check which things we need to do */
966 read_headers = FALSE;
967 read_body = FALSE;
969 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
970 MatcherProp * matcher = (MatcherProp *) l->data;
972 if (matcherprop_criteria_headers(matcher))
973 read_headers = TRUE;
974 if (matcherprop_criteria_body(matcher))
975 read_body = TRUE;
976 if (matcherprop_criteria_message(matcher)) {
977 read_headers = TRUE;
978 read_body = TRUE;
980 matcher->result = FALSE;
983 if (!read_headers && !read_body)
984 return result;
986 file = is_incorporating ? g_strdup(info->folder)
987 : procmsg_get_message_file(info);
989 if (file == NULL)
990 return FALSE;
992 if ((fp = fopen(file, "r")) == NULL) {
993 FILE_OP_ERROR(file, "fopen");
994 g_free(file);
995 return result;
998 /* read the headers */
1000 if (read_headers) {
1001 if (matcherlist_match_headers(matchers, fp))
1002 read_body = FALSE;
1004 else {
1005 matcherlist_skip_headers(fp);
1008 /* read the body */
1009 if (read_body) {
1010 matcherlist_match_body(matchers, fp);
1013 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1014 MatcherProp * matcher = (MatcherProp *) l->data;
1016 if (matcherprop_criteria_headers(matcher) ||
1017 matcherprop_criteria_body(matcher) ||
1018 matcherprop_criteria_message(matcher))
1019 if (matcher->result) {
1020 if (!matchers->bool_and) {
1021 result = TRUE;
1022 break;
1025 else {
1026 if (matchers->bool_and) {
1027 result = FALSE;
1028 break;
1033 g_free(file);
1035 fclose(fp);
1037 return result;
1040 /* test a list of condition */
1042 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
1044 GSList * l;
1045 gboolean result;
1047 if (matchers->bool_and)
1048 result = TRUE;
1049 else
1050 result = FALSE;
1052 /* test the cached elements */
1054 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1055 MatcherProp * matcher = (MatcherProp *) l->data;
1057 switch(matcher->criteria) {
1058 case MATCHING_ALL:
1059 case MATCHING_UNREAD:
1060 case MATCHING_NOT_UNREAD:
1061 case MATCHING_NEW:
1062 case MATCHING_NOT_NEW:
1063 case MATCHING_MARKED:
1064 case MATCHING_NOT_MARKED:
1065 case MATCHING_DELETED:
1066 case MATCHING_NOT_DELETED:
1067 case MATCHING_REPLIED:
1068 case MATCHING_NOT_REPLIED:
1069 case MATCHING_FORWARDED:
1070 case MATCHING_NOT_FORWARDED:
1071 case MATCHING_SUBJECT:
1072 case MATCHING_NOT_SUBJECT:
1073 case MATCHING_FROM:
1074 case MATCHING_NOT_FROM:
1075 case MATCHING_TO:
1076 case MATCHING_NOT_TO:
1077 case MATCHING_CC:
1078 case MATCHING_NOT_CC:
1079 case MATCHING_TO_OR_CC:
1080 case MATCHING_NOT_TO_AND_NOT_CC:
1081 case MATCHING_AGE_GREATER:
1082 case MATCHING_AGE_LOWER:
1083 case MATCHING_NEWSGROUPS:
1084 case MATCHING_NOT_NEWSGROUPS:
1085 case MATCHING_INREPLYTO:
1086 case MATCHING_NOT_INREPLYTO:
1087 case MATCHING_REFERENCES:
1088 case MATCHING_NOT_REFERENCES:
1089 case MATCHING_SCORE_GREATER:
1090 case MATCHING_SCORE_LOWER:
1091 case MATCHING_EXECUTE:
1092 case MATCHING_NOT_EXECUTE:
1093 if (matcherprop_match(matcher, info)) {
1094 if (!matchers->bool_and) {
1095 return TRUE;
1098 else {
1099 if (matchers->bool_and) {
1100 return FALSE;
1106 /* test the condition on the file */
1108 if (matcherlist_match_file(matchers, info, result)) {
1109 if (!matchers->bool_and)
1110 return TRUE;
1112 else {
1113 if (matchers->bool_and)
1114 return FALSE;
1117 return result;
1120 #if 0
1121 static void matcherprop_print(MatcherProp * matcher)
1123 int i;
1125 if (matcher == NULL) {
1126 printf("no matcher\n");
1127 return;
1130 switch (matcher->matchtype) {
1131 case MATCHING_MATCH:
1132 printf("match\n");
1133 break;
1134 case MATCHING_REGEXP:
1135 printf("regexp\n");
1136 break;
1137 case MATCHING_MATCHCASE:
1138 printf("matchcase\n");
1139 break;
1140 case MATCHING_REGEXPCASE:
1141 printf("regexpcase\n");
1142 break;
1145 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1146 i++) {
1147 if (matchparser_tab[i].id == matcher->criteria)
1148 printf("%s\n", matchparser_tab[i].str);
1151 if (matcher->expr)
1152 printf("expr : %s\n", matcher->expr);
1154 printf("age: %i\n", matcher->value;
1156 printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no");
1157 printf("error: %i\n", matcher->error);
1159 #endif
1161 gchar * matcherprop_to_string(MatcherProp * matcher)
1163 gchar * matcher_str = NULL;
1164 gchar * criteria_str;
1165 gchar * matchtype_str;
1166 int i;
1167 gchar * p;
1168 gint count;
1169 gchar * expr_str;
1170 gchar * out;
1172 criteria_str = NULL;
1173 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1174 i++) {
1175 if (matchparser_tab[i].id == matcher->criteria)
1176 criteria_str = matchparser_tab[i].str;
1178 if (criteria_str == NULL)
1179 return NULL;
1181 switch(matcher->criteria) {
1182 case MATCHING_AGE_GREATER:
1183 case MATCHING_AGE_LOWER:
1184 case MATCHING_SCORE_GREATER:
1185 case MATCHING_SCORE_LOWER:
1186 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1187 break;
1188 case MATCHING_ALL:
1189 case MATCHING_UNREAD:
1190 case MATCHING_NOT_UNREAD:
1191 case MATCHING_NEW:
1192 case MATCHING_NOT_NEW:
1193 case MATCHING_MARKED:
1194 case MATCHING_NOT_MARKED:
1195 case MATCHING_DELETED:
1196 case MATCHING_NOT_DELETED:
1197 case MATCHING_REPLIED:
1198 case MATCHING_NOT_REPLIED:
1199 case MATCHING_FORWARDED:
1200 case MATCHING_NOT_FORWARDED:
1201 return g_strdup(criteria_str);
1204 matchtype_str = NULL;
1205 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1206 i++) {
1207 if (matchparser_tab[i].id == matcher->matchtype)
1208 matchtype_str = matchparser_tab[i].str;
1211 if (matchtype_str == NULL)
1212 return NULL;
1214 switch (matcher->matchtype) {
1215 case MATCHING_MATCH:
1216 case MATCHING_MATCHCASE:
1217 count = 0;
1218 for(p = matcher->expr; *p != 0 ; p++)
1219 if (*p == '\"') count ++;
1221 expr_str = g_new(char, strlen(matcher->expr) + count + 1);
1223 for(p = matcher->expr, out = expr_str ; *p != 0 ; p++, out++) {
1224 if (*p == '\"') {
1225 *out = '\\'; out++;
1226 *out = '\"';
1228 else
1229 *out = *p;
1231 * out = '\0';
1233 if (matcher->header)
1234 matcher_str =
1235 g_strdup_printf("%s \"%s\" %s \"%s\"",
1236 criteria_str, matcher->header,
1237 matchtype_str, expr_str);
1238 else
1239 matcher_str =
1240 g_strdup_printf("%s %s \"%s\"", criteria_str,
1241 matchtype_str, expr_str);
1243 g_free(expr_str);
1245 break;
1247 case MATCHING_REGEXP:
1248 case MATCHING_REGEXPCASE:
1250 if (matcher->header)
1251 matcher_str =
1252 g_strdup_printf("%s \"%s\" %s /%s/",
1253 criteria_str, matcher->header,
1254 matchtype_str, matcher->expr);
1255 else
1256 matcher_str =
1257 g_strdup_printf("%s %s /%s/", criteria_str,
1258 matchtype_str, matcher->expr);
1260 break;
1263 return matcher_str;
1266 gchar * matcherlist_to_string(MatcherList * matchers)
1268 gint count;
1269 gchar ** vstr;
1270 GSList * l;
1271 gchar ** cur_str;
1272 gchar * result;
1274 count = g_slist_length(matchers->matchers);
1275 vstr = g_new(gchar *, count + 1);
1277 for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
1278 l = g_slist_next(l), cur_str ++) {
1279 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1280 if (*cur_str == NULL)
1281 break;
1283 *cur_str = NULL;
1285 if (matchers->bool_and)
1286 result = g_strjoinv(" & ", vstr);
1287 else
1288 result = g_strjoinv(" | ", vstr);
1290 for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1291 g_free(*cur_str);
1292 g_free(vstr);
1294 return result;
1297 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
1299 if (str)
1300 return strlen(str);
1301 else {
1302 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
1303 return 0;
1307 #define STRLEN_WITH_CHECK(expr) \
1308 strlen_with_check(#expr, __LINE__, expr)
1310 gchar * matching_build_command(gchar * cmd, MsgInfo * info)
1312 gchar * s = cmd;
1313 gchar * filename = NULL;
1314 gchar * processed_cmd;
1315 gchar * p;
1316 gint size;
1318 matcher_unescape_str(cmd);
1320 size = strlen(cmd) + 1;
1321 while (*s != '\0') {
1322 if (*s == '%') {
1323 s++;
1324 switch (*s) {
1325 case '%':
1326 size -= 1;
1327 break;
1328 case 's': /* subject */
1329 size += STRLEN_WITH_CHECK(info->subject) - 2;
1330 break;
1331 case 'f': /* from */
1332 size += STRLEN_WITH_CHECK(info->from) - 2;
1333 break;
1334 case 't': /* to */
1335 size += STRLEN_WITH_CHECK(info->to) - 2;
1336 break;
1337 case 'c': /* cc */
1338 size += STRLEN_WITH_CHECK(info->cc) - 2;
1339 break;
1340 case 'd': /* date */
1341 size += STRLEN_WITH_CHECK(info->date) - 2;
1342 break;
1343 case 'i': /* message-id */
1344 size += STRLEN_WITH_CHECK(info->msgid) - 2;
1345 break;
1346 case 'n': /* newsgroups */
1347 size += STRLEN_WITH_CHECK(info->newsgroups) - 2;
1348 break;
1349 case 'r': /* references */
1350 size += STRLEN_WITH_CHECK(info->references) - 2;
1351 break;
1352 case 'F': /* file */
1353 if (MSG_IS_FILTERING(info->flags))
1354 filename = g_strdup(info->folder);
1355 else
1356 filename = folder_item_fetch_msg(info->folder, info->msgnum);
1358 if (filename == NULL) {
1359 debug_print(_("%s(%d) - filename is not set"), __FILE__, __LINE__);
1360 return NULL;
1362 else
1363 size += strlen(filename) - 2;
1364 break;
1366 s++;
1368 else s++;
1371 processed_cmd = g_new0(gchar, size);
1372 s = cmd;
1373 p = processed_cmd;
1375 while (*s != '\0') {
1376 if (*s == '%') {
1377 s++;
1378 switch (*s) {
1379 case '%':
1380 *p = '%';
1381 p++;
1382 break;
1383 case 's': /* subject */
1384 if (info->subject != NULL)
1385 strcpy(p, info->subject);
1386 else
1387 strcpy(p, "(none)");
1388 p += strlen(p);
1389 break;
1390 case 'f': /* from */
1391 if (info->from != NULL)
1392 strcpy(p, info->from);
1393 else
1394 strcpy(p, "(none)");
1395 p += strlen(p);
1396 break;
1397 case 't': /* to */
1398 if (info->to != NULL)
1399 strcpy(p, info->to);
1400 else
1401 strcpy(p, "(none)");
1402 p += strlen(p);
1403 break;
1404 case 'c': /* cc */
1405 if (info->cc != NULL)
1406 strcpy(p, info->cc);
1407 else
1408 strcpy(p, "(none)");
1409 p += strlen(p);
1410 break;
1411 case 'd': /* date */
1412 if (info->date != NULL)
1413 strcpy(p, info->date);
1414 else
1415 strcpy(p, "(none)");
1416 p += strlen(p);
1417 break;
1418 case 'i': /* message-id */
1419 if (info->msgid != NULL)
1420 strcpy(p, info->msgid);
1421 else
1422 strcpy(p, "(none)");
1423 p += strlen(p);
1424 break;
1425 case 'n': /* newsgroups */
1426 if (info->newsgroups != NULL)
1427 strcpy(p, info->newsgroups);
1428 else
1429 strcpy(p, "(none)");
1430 p += strlen(p);
1431 break;
1432 case 'r': /* references */
1433 if (info->references != NULL)
1434 strcpy(p, info->references);
1435 else
1436 strcpy(p, "(none)");
1437 p += strlen(p);
1438 break;
1439 case 'F': /* file */
1440 strcpy(p, filename);
1441 p += strlen(p);
1442 break;
1443 default:
1444 *p = '%';
1445 p++;
1446 *p = *s;
1447 p++;
1448 break;
1450 s++;
1452 else {
1453 *p = *s;
1454 p++;
1455 s++;
1459 debug_print("*** exec string \"%s\"\n", processed_cmd);
1461 g_free(filename);
1462 return processed_cmd;
1466 /* ************************************************************ */
1469 static void matcher_parse (gchar * str)
1471 matcher_parser_scan_string(str);
1472 matcher_parserparse();