7 #include "procheader.h"
15 typedef struct _MatchParser MatchParser;
17 MatchParser matchparser_tab[] = {
19 {MATCHING_ALL, "all"},
20 {MATCHING_UNREAD, "unread"},
21 {MATCHING_NOT_UNREAD, "~unread"},
22 {MATCHING_NEW, "new"},
23 {MATCHING_NOT_NEW, "~new"},
24 {MATCHING_MARKED, "marked"},
25 {MATCHING_NOT_MARKED, "~marked"},
26 {MATCHING_DELETED, "deleted"},
27 {MATCHING_NOT_DELETED, "~deleted"},
28 {MATCHING_REPLIED, "replied"},
29 {MATCHING_NOT_REPLIED, "~replied"},
30 {MATCHING_FORWARDED, "forwarded"},
31 {MATCHING_NOT_FORWARDED, "~forwarded"},
34 {MATCHING_SUBJECT, "subject"},
35 {MATCHING_NOT_SUBJECT, "~subject"},
36 {MATCHING_FROM, "from"},
37 {MATCHING_NOT_FROM, "~from"},
39 {MATCHING_NOT_TO, "~to"},
41 {MATCHING_NOT_CC, "~cc"},
42 {MATCHING_TO_OR_CC, "to_or_cc"},
43 {MATCHING_NOT_TO_AND_NOT_CC, "~to_or_cc"},
44 {MATCHING_AGE_GREATER, "age_greater"},
45 {MATCHING_AGE_LOWER, "age_lower"},
46 {MATCHING_NEWSGROUPS, "newsgroups"},
47 {MATCHING_NOT_NEWSGROUPS, "~newsgroups"},
48 {MATCHING_INREPLYTO, "inreplyto"},
49 {MATCHING_NOT_INREPLYTO, "~inreplyto"},
50 {MATCHING_REFERENCES, "references"},
51 {MATCHING_NOT_REFERENCES, "~references"},
52 {MATCHING_SCORE_GREATER, "score_greater"},
53 {MATCHING_SCORE_LOWER, "score_lower"},
55 /* content have to be read */
56 {MATCHING_HEADER, "header"},
57 {MATCHING_NOT_HEADER, "~header"},
58 {MATCHING_HEADERS_PART, "headers_part"},
59 {MATCHING_NOT_HEADERS_PART, "~headers_part"},
60 {MATCHING_MESSAGE, "message"},
61 {MATCHING_NOT_MESSAGE, "~message"},
62 {MATCHING_BODY_PART, "body_part"},
63 {MATCHING_NOT_BODY_PART, "~body_part"},
66 {MATCHING_MATCHCASE, "matchcase"},
67 {MATCHING_MATCH, "match"},
68 {MATCHING_REGEXPCASE, "regexpcase"},
69 {MATCHING_REGEXP, "regexp"},
72 {MATCHING_SCORE, "score"},
75 {MATCHING_ACTION_MOVE, "move"},
76 {MATCHING_ACTION_COPY, "copy"},
77 {MATCHING_ACTION_DELETE, "delete"},
78 {MATCHING_ACTION_MARK, "mark"},
79 {MATCHING_ACTION_MARK_AS_READ, "mark_as_read"},
80 {MATCHING_ACTION_FORWARD, "forward"},
81 {MATCHING_ACTION_FORWARD_AS_ATTACHEMENT, "forward_as_attachement"},
82 {MATCHING_ACTION_FORWARD_NEWS, "forward_news"},
83 {MATCHING_ACTION_FORWARD_NEWS_AS_ATTACHEMENT, "forward_news_as_attachement"}
89 header "x-mailing" match "toto"
90 subject match "regexp" & to regexp "regexp"
91 subject match "regexp" | to regexpcase "regexp" | age_sup 5
94 static gboolean matcher_is_blank(gchar ch);
96 /* ******************* parser *********************** */
98 static gboolean matcher_is_blank(gchar ch)
100 return (ch == ' ') || (ch == '\t');
103 /* parse for one condition */
105 MatcherProp * matcherprop_parse(gchar ** str)
113 gchar * header = NULL;
116 key = matcher_parse_keyword(&tmp);
123 case MATCHING_AGE_LOWER:
124 case MATCHING_AGE_GREATER:
125 case MATCHING_SCORE_LOWER:
126 case MATCHING_SCORE_GREATER:
127 value = matcher_parse_number(&tmp);
134 prop = matcherprop_new(key, NULL, 0, NULL, value);
139 case MATCHING_UNREAD:
140 case MATCHING_NOT_UNREAD:
142 case MATCHING_NOT_NEW:
143 case MATCHING_MARKED:
144 case MATCHING_NOT_MARKED:
145 case MATCHING_DELETED:
146 case MATCHING_NOT_DELETED:
147 case MATCHING_REPLIED:
148 case MATCHING_NOT_REPLIED:
149 case MATCHING_FORWARDED:
150 case MATCHING_NOT_FORWARDED:
151 prop = matcherprop_new(key, NULL, 0, NULL, 0);
156 case MATCHING_SUBJECT:
157 case MATCHING_NOT_SUBJECT:
159 case MATCHING_NOT_FROM:
161 case MATCHING_NOT_TO:
163 case MATCHING_NOT_CC:
164 case MATCHING_TO_OR_CC:
165 case MATCHING_NOT_TO_AND_NOT_CC:
166 case MATCHING_NEWSGROUPS:
167 case MATCHING_NOT_NEWSGROUPS:
168 case MATCHING_INREPLYTO:
169 case MATCHING_NOT_REFERENCES:
170 case MATCHING_REFERENCES:
171 case MATCHING_NOT_INREPLYTO:
172 case MATCHING_MESSAGE:
173 case MATCHING_NOT_MESSAGE:
174 case MATCHING_HEADERS_PART:
175 case MATCHING_NOT_HEADERS_PART:
176 case MATCHING_BODY_PART:
177 case MATCHING_NOT_BODY_PART:
178 case MATCHING_HEADER:
179 case MATCHING_NOT_HEADER:
180 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
181 header = matcher_parse_str(&tmp);
188 match = matcher_parse_keyword(&tmp);
197 case MATCHING_REGEXP:
198 case MATCHING_REGEXPCASE:
199 expr = matcher_parse_regexp(&tmp);
207 prop = matcherprop_new(key, header, match, expr, 0);
212 case MATCHING_MATCHCASE:
213 expr = matcher_parse_str(&tmp);
221 prop = matcherprop_new(key, header, match, expr, 0);
237 gint matcher_parse_keyword(gchar ** str)
245 dup = alloca(strlen(* str) + 1);
249 while (matcher_is_blank(*p))
254 while (!matcher_is_blank(*p) && (*p != '\0'))
258 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
260 if ((strlen(matchparser_tab[i].str) == p - start) &&
261 (strncasecmp(matchparser_tab[i].str, start,
276 return matchparser_tab[match].id;
279 gint matcher_parse_number(gchar ** str)
285 dup = alloca(strlen(* str) + 1);
289 while (matcher_is_blank(*p))
294 if (!isdigit(*p) && *p != '-' && *p != '+') {
298 if (*p == '-' || *p == '+')
309 gboolean matcher_parse_boolean_op(gchar ** str)
315 while (matcher_is_blank(*p))
319 *str += p - * str + 1;
322 else if (*p == '&') {
323 *str += p - * str + 1;
332 gchar * matcher_parse_regexp(gchar ** str)
338 dup = alloca(strlen(* str) + 1);
342 while (matcher_is_blank(*p))
360 return g_strdup(start);
363 gchar * matcher_parse_str(gchar ** str)
370 dup = alloca(strlen(* str) + 1);
374 while (matcher_is_blank(*p))
397 *str += dest - dup + 2;
398 return g_strdup(start);
401 /* **************** data structure allocation **************** */
404 MatcherProp * matcherprop_new(gint criteria, gchar * header,
405 gint matchtype, gchar * expr,
410 prop = g_new0(MatcherProp, 1);
411 prop->criteria = criteria;
413 prop->header = g_strdup(header);
417 prop->expr = g_strdup(expr);
420 prop->matchtype = matchtype;
428 void matcherprop_free(MatcherProp * prop)
431 if (prop->preg != NULL) {
439 /* ************** match ******************************/
442 /* match the given string */
444 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
452 switch(prop->matchtype) {
453 case MATCHING_REGEXPCASE:
454 case MATCHING_REGEXP:
455 if (!prop->preg && (prop->error == 0)) {
456 prop->preg = g_new0(regex_t, 1);
457 if (regcomp(prop->preg, prop->expr,
458 REG_NOSUB | REG_EXTENDED
459 | ((prop->matchtype == MATCHING_REGEXPCASE)
460 ? REG_ICASE : 0)) != 0) {
465 if (prop->preg == NULL)
468 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
474 return (strstr(str, prop->expr) != NULL);
476 case MATCHING_MATCHCASE:
477 str2 = alloca(strlen(prop->expr) + 1);
478 strcpy(str2, prop->expr);
480 str1 = alloca(strlen(str) + 1);
483 return (strstr(str1, str2) != NULL);
490 /* match a message and his headers, hlist can be NULL if you don't
491 want to use headers */
493 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
497 switch(prop->criteria) {
500 case MATCHING_UNREAD:
501 return MSG_IS_UNREAD(info->flags);
502 case MATCHING_NOT_UNREAD:
503 return !MSG_IS_UNREAD(info->flags);
505 return MSG_IS_NEW(info->flags);
506 case MATCHING_NOT_NEW:
507 return !MSG_IS_NEW(info->flags);
508 case MATCHING_MARKED:
509 return MSG_IS_MARKED(info->flags);
510 case MATCHING_NOT_MARKED:
511 return !MSG_IS_MARKED(info->flags);
512 case MATCHING_DELETED:
513 return MSG_IS_DELETED(info->flags);
514 case MATCHING_NOT_DELETED:
515 return !MSG_IS_DELETED(info->flags);
516 case MATCHING_REPLIED:
517 return MSG_IS_REPLIED(info->flags);
518 case MATCHING_NOT_REPLIED:
519 return !MSG_IS_REPLIED(info->flags);
520 case MATCHING_FORWARDED:
521 return MSG_IS_FORWARDED(info->flags);
522 case MATCHING_NOT_FORWARDED:
523 return !MSG_IS_FORWARDED(info->flags);
524 case MATCHING_SUBJECT:
525 return matcherprop_string_match(prop, info->subject);
526 case MATCHING_NOT_SUBJECT:
527 return !matcherprop_string_match(prop, info->subject);
529 return matcherprop_string_match(prop, info->from);
530 case MATCHING_NOT_FROM:
531 return !matcherprop_string_match(prop, info->from);
533 return matcherprop_string_match(prop, info->to);
534 case MATCHING_NOT_TO:
535 return !matcherprop_string_match(prop, info->to);
537 return matcherprop_string_match(prop, info->cc);
538 case MATCHING_NOT_CC:
539 return !matcherprop_string_match(prop, info->cc);
540 case MATCHING_TO_OR_CC:
541 return matcherprop_string_match(prop, info->to)
542 || matcherprop_string_match(prop, info->cc);
543 case MATCHING_NOT_TO_AND_NOT_CC:
544 return !(matcherprop_string_match(prop, info->to)
545 || matcherprop_string_match(prop, info->cc));
546 case MATCHING_AGE_GREATER:
548 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
549 case MATCHING_AGE_LOWER:
551 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
552 case MATCHING_SCORE_GREATER:
553 return info->score >= prop->value;
554 case MATCHING_SCORE_LOWER:
555 return info->score <= prop->value;
556 case MATCHING_NEWSGROUPS:
557 return matcherprop_string_match(prop, info->newsgroups);
558 case MATCHING_NOT_NEWSGROUPS:
559 return !matcherprop_string_match(prop, info->newsgroups);
560 case MATCHING_INREPLYTO:
561 return matcherprop_string_match(prop, info->inreplyto);
562 case MATCHING_NOT_INREPLYTO:
563 return !matcherprop_string_match(prop, info->inreplyto);
564 case MATCHING_REFERENCES:
565 return matcherprop_string_match(prop, info->references);
566 case MATCHING_NOT_REFERENCES:
567 return !matcherprop_string_match(prop, info->references);
568 case MATCHING_HEADER:
574 /* ********************* MatcherList *************************** */
577 /* parse for a list of conditions */
579 MatcherList * matcherlist_parse(gchar ** str)
582 MatcherProp * matcher;
583 GSList * matchers_list = NULL;
584 gboolean bool_and = TRUE;
587 gboolean main_bool_and = TRUE;
592 matcher = matcherprop_parse(&tmp);
598 matchers_list = g_slist_append(matchers_list, matcher);
601 bool_and = matcher_parse_boolean_op(&tmp);
607 main_bool_and = bool_and;
608 matcher = matcherprop_parse(&tmp);
611 g_slist_append(matchers_list, matcher);
614 for(l = matchers_list ; l != NULL ;
616 matcherprop_free((MatcherProp *)
618 g_slist_free(matchers_list);
625 cond = matcherlist_new(matchers_list, main_bool_and);
632 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
636 cond = g_new0(MatcherList, 1);
638 cond->matchers = matchers;
639 cond->bool_and = bool_and;
644 void matcherlist_free(MatcherList * cond)
648 for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
649 matcherprop_free((MatcherProp *) l->data);
658 static void matcherlist_skip_headers(FILE *fp)
662 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
667 matcherprop_match_one_header
668 returns TRUE if buf matchs the MatchersProp criteria
671 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
677 switch(matcher->criteria) {
678 case MATCHING_HEADER:
679 case MATCHING_NOT_HEADER:
680 header = procheader_parse_header(buf);
681 if (procheader_headername_equal(header->name,
683 if (matcher->criteria == MATCHING_HEADER)
684 result = matcherprop_string_match(matcher, header->body);
686 result = !matcherprop_string_match(matcher, header->body);
687 procheader_header_free(header);
691 case MATCHING_HEADERS_PART:
692 case MATCHING_MESSAGE:
693 return matcherprop_string_match(matcher, buf);
694 case MATCHING_NOT_MESSAGE:
695 case MATCHING_NOT_HEADERS_PART:
696 return !matcherprop_string_match(matcher, buf);
702 matcherprop_criteria_header
703 returns TRUE if the headers must be matched
706 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
708 switch(matcher->criteria) {
709 case MATCHING_HEADER:
710 case MATCHING_NOT_HEADER:
711 case MATCHING_HEADERS_PART:
712 case MATCHING_NOT_HEADERS_PART:
713 case MATCHING_MESSAGE:
714 case MATCHING_NOT_MESSAGE:
722 matcherlist_match_one_header
723 returns TRUE if buf matchs the MatchersList criteria
726 static gboolean matcherlist_match_one_header(MatcherList * matchers,
727 gchar * buf, gboolean result)
731 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
732 MatcherProp * matcher = (MatcherProp *) l->data;
734 if (matcherprop_criteria_headers(matcher)) {
735 if (matcherprop_match_one_header(matcher, buf)) {
736 if (!matchers->bool_and)
740 if (matchers->bool_and)
750 matcherlist_match_headers
751 returns TRUE if one of the headers matchs the MatcherList criteria
754 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp,
759 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
760 if (matcherlist_match_one_header(matchers, buf, result)) {
761 if (!matchers->bool_and)
765 if (matchers->bool_and)
773 matcherprop_criteria_body
774 returns TRUE if the body must be matched
777 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
779 switch(matcher->criteria) {
780 case MATCHING_BODY_PART:
781 case MATCHING_NOT_BODY_PART:
782 case MATCHING_MESSAGE:
783 case MATCHING_NOT_MESSAGE:
791 matcherprop_match_line
792 returns TRUE if the string matchs the MatcherProp criteria
795 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
797 switch(matcher->criteria) {
798 case MATCHING_BODY_PART:
799 case MATCHING_MESSAGE:
800 return matcherprop_string_match(matcher, line);
801 case MATCHING_NOT_BODY_PART:
802 case MATCHING_NOT_MESSAGE:
803 return !matcherprop_string_match(matcher, line);
809 matcherlist_match_line
810 returns TRUE if the string matchs the MatcherList criteria
813 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line,
818 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
819 MatcherProp * matcher = (MatcherProp *) l->data;
821 if (matcherprop_criteria_body(matcher)) {
822 if (matcherprop_match_line(matcher, line)) {
823 if (!matchers->bool_and)
827 if (matchers->bool_and)
836 matcherlist_match_body
837 returns TRUE if one line of the body matchs the MatcherList criteria
840 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp,
845 while (fgets(buf, sizeof(buf), fp) != NULL) {
846 if (matcherlist_match_line(matchers, buf, result)) {
847 if (!matchers->bool_and)
851 if (matchers->bool_and)
858 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
861 gboolean read_headers;
867 /* file need to be read ? */
869 read_headers = FALSE;
871 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
872 MatcherProp * matcher = (MatcherProp *) l->data;
874 if (matcherprop_criteria_headers(matcher))
876 if (matcherprop_criteria_body(matcher))
880 if (!read_headers && !read_body)
883 file = procmsg_get_message_file(info);
887 if ((fp = fopen(file, "r")) == NULL) {
888 FILE_OP_ERROR(file, "fopen");
893 /* read the headers */
896 if (matcherlist_match_headers(matchers, fp, result)) {
897 if (!matchers->bool_and)
901 if (matchers->bool_and)
906 matcherlist_skip_headers(fp);
911 if (matcherlist_match_body(matchers, fp, result)) {
912 if (!matchers->bool_and)
916 if (matchers->bool_and)
928 /* test a list of condition */
930 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
935 if (matchers->bool_and)
940 /* test the condition on the file */
942 if (matcherlist_match_file(matchers, info, result)) {
943 if (!matchers->bool_and)
947 if (matchers->bool_and)
951 /* test the cached elements */
953 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
954 MatcherProp * matcher = (MatcherProp *) l->data;
956 if (matcherprop_match(matcher, info)) {
957 if (!matchers->bool_and) {
963 if (matchers->bool_and) {
974 static void matcherprop_print(MatcherProp * matcher)
978 if (matcher == NULL) {
979 printf("no matcher\n");
983 switch (matcher->matchtype) {
987 case MATCHING_REGEXP:
990 case MATCHING_MATCHCASE:
991 printf("matchcase\n");
993 case MATCHING_REGEXPCASE:
994 printf("regexpcase\n");
998 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1000 if (matchparser_tab[i].id == matcher->criteria)
1001 printf("%s\n", matchparser_tab[i].str);
1005 printf("expr : %s\n", matcher->expr);
1007 printf("age: %i\n", matcher->value;
1009 printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no");
1010 printf("error: %i\n", matcher->error);
1014 gchar * matcherprop_to_string(MatcherProp * matcher)
1016 gchar * matcher_str = NULL;
1017 gchar * criteria_str;
1018 gchar * matchtype_str;
1025 criteria_str = NULL;
1026 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1028 if (matchparser_tab[i].id == matcher->criteria)
1029 criteria_str = matchparser_tab[i].str;
1031 if (criteria_str == NULL)
1034 switch(matcher->criteria) {
1035 case MATCHING_AGE_GREATER:
1036 case MATCHING_AGE_LOWER:
1037 case MATCHING_SCORE_GREATER:
1038 case MATCHING_SCORE_LOWER:
1039 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1042 case MATCHING_UNREAD:
1043 case MATCHING_NOT_UNREAD:
1045 case MATCHING_NOT_NEW:
1046 case MATCHING_MARKED:
1047 case MATCHING_NOT_MARKED:
1048 case MATCHING_DELETED:
1049 case MATCHING_NOT_DELETED:
1050 case MATCHING_REPLIED:
1051 case MATCHING_NOT_REPLIED:
1052 case MATCHING_FORWARDED:
1053 case MATCHING_NOT_FORWARDED:
1054 return g_strdup(criteria_str);
1057 matchtype_str = NULL;
1058 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1060 if (matchparser_tab[i].id == matcher->matchtype)
1061 matchtype_str = matchparser_tab[i].str;
1064 if (matchtype_str == NULL)
1067 switch (matcher->matchtype) {
1068 case MATCHING_MATCH:
1069 case MATCHING_MATCHCASE:
1071 for(p = matcher->expr; *p != 0 ; p++)
1072 if (*p == '\"') count ++;
1074 expr_str = g_new(char, strlen(matcher->expr) + count + 1);
1076 for(p = matcher->expr, out = expr_str ; *p != 0 ; p++, out++) {
1086 if (matcher->header)
1088 g_strdup_printf("%s \"%s\" %s \"%s\"",
1089 criteria_str, matcher->header,
1090 matchtype_str, expr_str);
1093 g_strdup_printf("%s %s \"%s\"", criteria_str,
1094 matchtype_str, expr_str);
1100 case MATCHING_REGEXP:
1101 case MATCHING_REGEXPCASE:
1103 if (matcher->header)
1105 g_strdup_printf("%s \"%s\" %s /%s/",
1106 criteria_str, matcher->header,
1107 matchtype_str, matcher->expr);
1110 g_strdup_printf("%s %s /%s/", criteria_str,
1111 matchtype_str, matcher->expr);
1119 gchar * matcherlist_to_string(MatcherList * matchers)
1127 count = g_slist_length(matchers->matchers);
1128 vstr = g_new(gchar *, count + 1);
1130 for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
1131 l = g_slist_next(l), cur_str ++) {
1132 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1133 if (*cur_str == NULL)
1138 if (matchers->bool_and)
1139 result = g_strjoinv(" & ", vstr);
1141 result = g_strjoinv(" | ", vstr);
1143 for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)