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"},
51 /* content have to be read */
52 {MATCHING_HEADER, "header"},
53 {MATCHING_NOT_HEADER, "~header"},
54 {MATCHING_HEADERS_PART, "headers_part"},
55 {MATCHING_NOT_HEADERS_PART, "~headers_part"},
56 {MATCHING_MESSAGE, "message"},
57 {MATCHING_NOT_MESSAGE, "~message"},
58 {MATCHING_BODY_PART, "body_part"},
59 {MATCHING_NOT_BODY_PART, "~body_part"},
62 {MATCHING_MATCHCASE, "matchcase"},
63 {MATCHING_MATCH, "match"},
64 {MATCHING_REGEXPCASE, "regexpcase"},
65 {MATCHING_REGEXP, "regexp"},
68 {MATCHING_SCORE, "score"},
71 {MATCHING_ACTION_MOVE, "move"},
72 {MATCHING_ACTION_COPY, "copy"},
73 {MATCHING_ACTION_DELETE, "delete"},
74 {MATCHING_ACTION_MARK, "mark"},
75 {MATCHING_ACTION_MARK_AS_READ, "mark_as_read"},
76 {MATCHING_ACTION_FORWARD, "forward"},
77 {MATCHING_ACTION_FORWARD_AS_ATTACHEMENT, "forward_as_attachement"},
78 {MATCHING_ACTION_FORWARD_NEWS, "forward_news"},
79 {MATCHING_ACTION_FORWARD_NEWS_AS_ATTACHEMENT, "forward_news_as_attachement"}
85 header "x-mailing" match "toto"
86 subject match "regexp" & to regexp "regexp"
87 subject match "regexp" | to regexpcase "regexp" | age_sup 5
90 static gboolean matcher_is_blank(gchar ch);
92 /* ******************* parser *********************** */
94 static gboolean matcher_is_blank(gchar ch)
96 return (ch == ' ') || (ch == '\t');
99 /* parse for one condition */
101 MatcherProp * matcherprop_parse(gchar ** str)
109 gchar * header = NULL;
112 key = matcher_parse_keyword(&tmp);
119 case MATCHING_AGE_LOWER:
120 case MATCHING_AGE_GREATER:
121 age = matcher_parse_number(&tmp);
128 prop = matcherprop_new(key, NULL, 0, NULL, age);
133 case MATCHING_UNREAD:
134 case MATCHING_NOT_UNREAD:
136 case MATCHING_NOT_NEW:
137 case MATCHING_MARKED:
138 case MATCHING_NOT_MARKED:
139 case MATCHING_DELETED:
140 case MATCHING_NOT_DELETED:
141 case MATCHING_REPLIED:
142 case MATCHING_NOT_REPLIED:
143 case MATCHING_FORWARDED:
144 case MATCHING_NOT_FORWARDED:
145 prop = matcherprop_new(key, NULL, 0, NULL, 0);
150 case MATCHING_SUBJECT:
151 case MATCHING_NOT_SUBJECT:
153 case MATCHING_NOT_FROM:
155 case MATCHING_NOT_TO:
157 case MATCHING_NOT_CC:
158 case MATCHING_TO_OR_CC:
159 case MATCHING_NOT_TO_AND_NOT_CC:
160 case MATCHING_NEWSGROUPS:
161 case MATCHING_NOT_NEWSGROUPS:
162 case MATCHING_INREPLYTO:
163 case MATCHING_NOT_INREPLYTO:
164 case MATCHING_MESSAGE:
165 case MATCHING_NOT_MESSAGE:
166 case MATCHING_HEADERS_PART:
167 case MATCHING_NOT_HEADERS_PART:
168 case MATCHING_BODY_PART:
169 case MATCHING_NOT_BODY_PART:
170 case MATCHING_HEADER:
171 case MATCHING_NOT_HEADER:
172 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
173 header = matcher_parse_str(&tmp);
180 match = matcher_parse_keyword(&tmp);
189 case MATCHING_REGEXP:
190 case MATCHING_REGEXPCASE:
191 expr = matcher_parse_regexp(&tmp);
199 prop = matcherprop_new(key, header, match, expr, 0);
204 case MATCHING_MATCHCASE:
205 expr = matcher_parse_str(&tmp);
213 prop = matcherprop_new(key, header, match, expr, 0);
229 gint matcher_parse_keyword(gchar ** str)
237 dup = alloca(strlen(* str) + 1);
241 while (matcher_is_blank(*p))
246 while (!matcher_is_blank(*p) && (*p != '\0'))
250 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
252 if (strncasecmp(matchparser_tab[i].str, start,
267 return matchparser_tab[match].id;
270 gint matcher_parse_number(gchar ** str)
276 dup = alloca(strlen(* str) + 1);
280 while (matcher_is_blank(*p))
285 if (!isdigit(*p) && *p != '-' && *p != '+') {
289 if (*p == '-' || *p == '+')
300 gboolean matcher_parse_boolean_op(gchar ** str)
306 while (matcher_is_blank(*p))
310 *str += p - * str + 1;
313 else if (*p == '&') {
314 *str += p - * str + 1;
323 gchar * matcher_parse_regexp(gchar ** str)
329 dup = alloca(strlen(* str) + 1);
333 while (matcher_is_blank(*p))
351 return g_strdup(start);
354 gchar * matcher_parse_str(gchar ** str)
361 dup = alloca(strlen(* str) + 1);
365 while (matcher_is_blank(*p))
388 *str += dest - dup + 2;
389 return g_strdup(start);
392 /* **************** data structure allocation **************** */
395 MatcherProp * matcherprop_new(gint criteria, gchar * header,
396 gint matchtype, gchar * expr,
401 prop = g_new0(MatcherProp, 1);
402 prop->criteria = criteria;
404 prop->header = g_strdup(header);
408 prop->expr = g_strdup(expr);
411 prop->matchtype = matchtype;
419 void matcherprop_free(MatcherProp * prop)
422 if (prop->preg != NULL) {
430 /* ************** match ******************************/
433 /* match the given string */
435 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
443 switch(prop->matchtype) {
444 case MATCHING_REGEXPCASE:
445 case MATCHING_REGEXP:
446 if (!prop->preg && (prop->error == 0)) {
447 prop->preg = g_new0(regex_t, 1);
448 if (regcomp(prop->preg, prop->expr,
449 REG_NOSUB | REG_EXTENDED
450 | ((prop->matchtype == MATCHING_REGEXPCASE)
451 ? REG_ICASE : 0)) != 0) {
456 if (prop->preg == NULL)
459 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
465 return (strstr(str, prop->expr) != NULL);
467 case MATCHING_MATCHCASE:
468 str2 = alloca(strlen(prop->expr) + 1);
469 strcpy(str2, prop->expr);
471 str1 = alloca(strlen(str) + 1);
474 return (strstr(str1, str2) != NULL);
481 /* match a message and his headers, hlist can be NULL if you don't
482 want to use headers */
484 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
488 switch(prop->criteria) {
491 case MATCHING_UNREAD:
492 return MSG_IS_UNREAD(info->flags);
493 case MATCHING_NOT_UNREAD:
494 return !MSG_IS_UNREAD(info->flags);
496 return MSG_IS_NEW(info->flags);
497 case MATCHING_NOT_NEW:
498 return !MSG_IS_NEW(info->flags);
499 case MATCHING_MARKED:
500 return MSG_IS_MARKED(info->flags);
501 case MATCHING_NOT_MARKED:
502 return !MSG_IS_MARKED(info->flags);
503 case MATCHING_DELETED:
504 return MSG_IS_DELETED(info->flags);
505 case MATCHING_NOT_DELETED:
506 return !MSG_IS_DELETED(info->flags);
507 case MATCHING_REPLIED:
508 return MSG_IS_REPLIED(info->flags);
509 case MATCHING_NOT_REPLIED:
510 return !MSG_IS_REPLIED(info->flags);
511 case MATCHING_FORWARDED:
512 return MSG_IS_FORWARDED(info->flags);
513 case MATCHING_NOT_FORWARDED:
514 return !MSG_IS_FORWARDED(info->flags);
515 case MATCHING_SUBJECT:
516 return matcherprop_string_match(prop, info->subject);
517 case MATCHING_NOT_SUBJECT:
518 return !matcherprop_string_match(prop, info->subject);
520 return matcherprop_string_match(prop, info->from);
521 case MATCHING_NOT_FROM:
522 return !matcherprop_string_match(prop, info->from);
524 return matcherprop_string_match(prop, info->to);
525 case MATCHING_NOT_TO:
526 return !matcherprop_string_match(prop, info->to);
528 return matcherprop_string_match(prop, info->cc);
529 case MATCHING_NOT_CC:
530 return !matcherprop_string_match(prop, info->cc);
531 case MATCHING_TO_OR_CC:
532 return matcherprop_string_match(prop, info->to)
533 || matcherprop_string_match(prop, info->cc);
534 case MATCHING_NOT_TO_AND_NOT_CC:
535 return !(matcherprop_string_match(prop, info->to)
536 || matcherprop_string_match(prop, info->cc));
537 case MATCHING_AGE_GREATER:
539 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->age;
540 case MATCHING_AGE_LOWER:
542 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->age;
543 case MATCHING_NEWSGROUPS:
544 return matcherprop_string_match(prop, info->newsgroups);
545 case MATCHING_NOT_NEWSGROUPS:
546 return !matcherprop_string_match(prop, info->newsgroups);
547 case MATCHING_INREPLYTO:
548 return matcherprop_string_match(prop, info->inreplyto);
549 case MATCHING_NOT_INREPLYTO:
550 return !matcherprop_string_match(prop, info->inreplyto);
551 case MATCHING_HEADER:
557 /* ********************* MatcherList *************************** */
560 /* parse for a list of conditions */
562 MatcherList * matcherlist_parse(gchar ** str)
565 MatcherProp * matcher;
566 GSList * matchers_list = NULL;
567 gboolean bool_and = TRUE;
570 gboolean main_bool_and = TRUE;
575 matcher = matcherprop_parse(&tmp);
581 matchers_list = g_slist_append(matchers_list, matcher);
584 bool_and = matcher_parse_boolean_op(&tmp);
590 main_bool_and = bool_and;
591 matcher = matcherprop_parse(&tmp);
594 g_slist_append(matchers_list, matcher);
597 for(l = matchers_list ; l != NULL ;
599 matcherprop_free((MatcherProp *)
601 g_slist_free(matchers_list);
608 cond = matcherlist_new(matchers_list, main_bool_and);
615 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
619 cond = g_new0(MatcherList, 1);
621 cond->matchers = matchers;
622 cond->bool_and = bool_and;
627 void matcherlist_free(MatcherList * cond)
631 for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
632 matcherprop_free((MatcherProp *) l->data);
641 static void matcherlist_skip_headers(FILE *fp)
645 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
650 matcherprop_match_one_header
651 returns TRUE if buf matchs the MatchersProp criteria
654 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
660 switch(matcher->criteria) {
661 case MATCHING_HEADER:
662 case MATCHING_NOT_HEADER:
663 header = procheader_parse_header(buf);
664 if (procheader_headername_equal(header->name,
666 if (matcher->criteria == MATCHING_HEADER)
667 result = matcherprop_string_match(matcher, header->body);
669 result = !matcherprop_string_match(matcher, header->body);
670 procheader_header_free(header);
674 case MATCHING_HEADERS_PART:
675 case MATCHING_MESSAGE:
676 return matcherprop_string_match(matcher, buf);
677 case MATCHING_NOT_MESSAGE:
678 case MATCHING_NOT_HEADERS_PART:
679 return !matcherprop_string_match(matcher, buf);
685 matcherprop_criteria_header
686 returns TRUE if the headers must be matched
689 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
691 switch(matcher->criteria) {
692 case MATCHING_HEADER:
693 case MATCHING_NOT_HEADER:
694 case MATCHING_HEADERS_PART:
695 case MATCHING_NOT_HEADERS_PART:
696 case MATCHING_MESSAGE:
697 case MATCHING_NOT_MESSAGE:
705 matcherlist_match_one_header
706 returns TRUE if buf matchs the MatchersList criteria
709 static gboolean matcherlist_match_one_header(MatcherList * matchers,
710 gchar * buf, gboolean result)
714 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
715 MatcherProp * matcher = (MatcherProp *) l->data;
717 if (matcherprop_criteria_headers(matcher)) {
718 if (matcherprop_match_one_header(matcher, buf)) {
719 if (!matchers->bool_and)
723 if (matchers->bool_and)
733 matcherlist_match_headers
734 returns TRUE if one of the headers matchs the MatcherList criteria
737 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp,
742 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
743 if (matcherlist_match_one_header(matchers, buf, result)) {
744 if (!matchers->bool_and)
748 if (matchers->bool_and)
756 matcherprop_criteria_body
757 returns TRUE if the body must be matched
760 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
762 switch(matcher->criteria) {
763 case MATCHING_BODY_PART:
764 case MATCHING_NOT_BODY_PART:
765 case MATCHING_MESSAGE:
766 case MATCHING_NOT_MESSAGE:
774 matcherprop_match_line
775 returns TRUE if the string matchs the MatcherProp criteria
778 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
780 switch(matcher->criteria) {
781 case MATCHING_BODY_PART:
782 case MATCHING_MESSAGE:
783 return matcherprop_string_match(matcher, line);
784 case MATCHING_NOT_BODY_PART:
785 case MATCHING_NOT_MESSAGE:
786 return !matcherprop_string_match(matcher, line);
792 matcherlist_match_line
793 returns TRUE if the string matchs the MatcherList criteria
796 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line,
801 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
802 MatcherProp * matcher = (MatcherProp *) l->data;
804 if (matcherprop_criteria_body(matcher)) {
805 if (matcherprop_match_line(matcher, line)) {
806 if (!matchers->bool_and)
810 if (matchers->bool_and)
819 matcherlist_match_body
820 returns TRUE if one line of the body matchs the MatcherList criteria
823 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp,
828 while (fgets(buf, sizeof(buf), fp) != NULL) {
829 if (matcherlist_match_line(matchers, buf, result)) {
830 if (!matchers->bool_and)
834 if (matchers->bool_and)
841 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
844 gboolean read_headers;
850 /* file need to be read ? */
852 read_headers = FALSE;
854 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
855 MatcherProp * matcher = (MatcherProp *) l->data;
857 if (matcherprop_criteria_headers(matcher))
859 if (matcherprop_criteria_body(matcher))
863 if (!read_headers && !read_body)
866 file = procmsg_get_message_file(info);
870 if ((fp = fopen(file, "r")) == NULL) {
871 FILE_OP_ERROR(file, "fopen");
876 /* read the headers */
879 if (matcherlist_match_headers(matchers, fp, result)) {
880 if (!matchers->bool_and)
884 if (matchers->bool_and)
889 matcherlist_skip_headers(fp);
894 if (matcherlist_match_body(matchers, fp, result)) {
895 if (!matchers->bool_and)
899 if (matchers->bool_and)
911 /* test a list of condition */
913 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
918 if (matchers->bool_and)
923 /* test the condition on the file */
925 if (matcherlist_match_file(matchers, info, result)) {
926 if (!matchers->bool_and)
930 if (matchers->bool_and)
934 /* test the cached elements */
936 for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
937 MatcherProp * matcher = (MatcherProp *) l->data;
939 if (matcherprop_match(matcher, info)) {
940 if (!matchers->bool_and) {
946 if (matchers->bool_and) {
957 static void matcherprop_print(MatcherProp * matcher)
961 if (matcher == NULL) {
962 printf("no matcher\n");
966 switch (matcher->matchtype) {
970 case MATCHING_REGEXP:
973 case MATCHING_MATCHCASE:
974 printf("matchcase\n");
976 case MATCHING_REGEXPCASE:
977 printf("regexpcase\n");
981 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
983 if (matchparser_tab[i].id == matcher->criteria)
984 printf("%s\n", matchparser_tab[i].str);
988 printf("expr : %s\n", matcher->expr);
990 printf("age: %i\n", matcher->age);
992 printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no");
993 printf("error: %i\n", matcher->error);
997 gchar * matcherprop_to_string(MatcherProp * matcher)
999 gchar * matcher_str = NULL;
1000 gchar * criteria_str;
1001 gchar * matchtype_str;
1008 criteria_str = NULL;
1009 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1011 if (matchparser_tab[i].id == matcher->criteria)
1012 criteria_str = matchparser_tab[i].str;
1014 if (criteria_str == NULL)
1017 switch(matcher->criteria) {
1018 case MATCHING_AGE_GREATER:
1019 case MATCHING_AGE_LOWER:
1020 return g_strdup_printf("%s %i", criteria_str, matcher->age);
1023 case MATCHING_UNREAD:
1024 case MATCHING_NOT_UNREAD:
1026 case MATCHING_NOT_NEW:
1027 case MATCHING_MARKED:
1028 case MATCHING_NOT_MARKED:
1029 case MATCHING_DELETED:
1030 case MATCHING_NOT_DELETED:
1031 case MATCHING_REPLIED:
1032 case MATCHING_NOT_REPLIED:
1033 case MATCHING_FORWARDED:
1034 case MATCHING_NOT_FORWARDED:
1035 return g_strdup(criteria_str);
1038 matchtype_str = NULL;
1039 for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
1041 if (matchparser_tab[i].id == matcher->matchtype)
1042 matchtype_str = matchparser_tab[i].str;
1045 if (matchtype_str == NULL)
1048 switch (matcher->matchtype) {
1049 case MATCHING_MATCH:
1050 case MATCHING_MATCHCASE:
1052 for(p = matcher->expr; *p != 0 ; p++)
1053 if (*p == '\"') count ++;
1055 expr_str = g_new(char, strlen(matcher->expr) + count + 1);
1057 for(p = matcher->expr, out = expr_str ; *p != 0 ; p++, out++) {
1067 if (matcher->header)
1069 g_strdup_printf("%s \"%s\" %s \"%s\"",
1070 criteria_str, matcher->header,
1071 matchtype_str, expr_str);
1074 g_strdup_printf("%s %s \"%s\"", criteria_str,
1075 matchtype_str, expr_str);
1081 case MATCHING_REGEXP:
1082 case MATCHING_REGEXPCASE:
1084 if (matcher->header)
1086 g_strdup_printf("%s \"%s\" %s /%s/",
1087 criteria_str, matcher->header,
1088 matchtype_str, matcher->expr);
1091 g_strdup_printf("%s %s /%s/", criteria_str,
1092 matchtype_str, matcher->expr);
1100 gchar * matcherlist_to_string(MatcherList * matchers)
1108 count = g_slist_length(matchers->matchers);
1109 vstr = g_new(gchar *, count + 1);
1111 for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
1112 l = g_slist_next(l), cur_str ++) {
1113 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1114 if (*cur_str == NULL)
1119 if (matchers->bool_and)
1120 result = g_strjoinv(" & ", vstr);
1122 result = g_strjoinv(" | ", vstr);
1124 for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)