2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2012 by the Claws Mail Team and Hiroyuki Yamamoto
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 3 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, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
26 #include <glib/gi18n.h>
38 #include "procheader.h"
40 #include "matcher_parser.h"
41 #include "prefs_gtk.h"
42 #include "addr_compl.h"
44 #include "quoted-printable.h"
47 #include "prefs_common.h"
50 #include "folder_item_prefs.h"
54 *\brief Keyword lookup element
57 gint id; /*!< keyword id */
58 gchar *str; /*!< keyword */
60 typedef struct _MatchParser MatchParser;
63 *\brief Table with strings and ids used by the lexer and
64 * the parser. New keywords can be added here.
66 static const MatchParser matchparser_tab[] = {
68 {MATCHCRITERIA_ALL, "all"},
69 {MATCHCRITERIA_UNREAD, "unread"},
70 {MATCHCRITERIA_NOT_UNREAD, "~unread"},
71 {MATCHCRITERIA_NEW, "new"},
72 {MATCHCRITERIA_NOT_NEW, "~new"},
73 {MATCHCRITERIA_MARKED, "marked"},
74 {MATCHCRITERIA_NOT_MARKED, "~marked"},
75 {MATCHCRITERIA_DELETED, "deleted"},
76 {MATCHCRITERIA_NOT_DELETED, "~deleted"},
77 {MATCHCRITERIA_REPLIED, "replied"},
78 {MATCHCRITERIA_NOT_REPLIED, "~replied"},
79 {MATCHCRITERIA_FORWARDED, "forwarded"},
80 {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
81 {MATCHCRITERIA_LOCKED, "locked"},
82 {MATCHCRITERIA_NOT_LOCKED, "~locked"},
83 {MATCHCRITERIA_COLORLABEL, "colorlabel"},
84 {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
85 {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
86 {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
87 {MATCHCRITERIA_WATCH_THREAD, "watch_thread"},
88 {MATCHCRITERIA_NOT_WATCH_THREAD, "~watch_thread"},
89 {MATCHCRITERIA_SPAM, "spam"},
90 {MATCHCRITERIA_NOT_SPAM, "~spam"},
91 {MATCHCRITERIA_HAS_ATTACHMENT, "has_attachment"},
92 {MATCHCRITERIA_HAS_NO_ATTACHMENT, "~has_attachment"},
93 {MATCHCRITERIA_SIGNED, "signed"},
94 {MATCHCRITERIA_NOT_SIGNED, "~signed"},
97 {MATCHCRITERIA_SUBJECT, "subject"},
98 {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
99 {MATCHCRITERIA_FROM, "from"},
100 {MATCHCRITERIA_NOT_FROM, "~from"},
101 {MATCHCRITERIA_TO, "to"},
102 {MATCHCRITERIA_NOT_TO, "~to"},
103 {MATCHCRITERIA_CC, "cc"},
104 {MATCHCRITERIA_NOT_CC, "~cc"},
105 {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
106 {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
107 {MATCHCRITERIA_TAG, "tag"},
108 {MATCHCRITERIA_NOT_TAG, "~tag"},
109 {MATCHCRITERIA_TAGGED, "tagged"},
110 {MATCHCRITERIA_NOT_TAGGED, "~tagged"},
111 {MATCHCRITERIA_AGE_GREATER, "age_greater"},
112 {MATCHCRITERIA_AGE_LOWER, "age_lower"},
113 {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
114 {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
115 {MATCHCRITERIA_INREPLYTO, "inreplyto"},
116 {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
117 {MATCHCRITERIA_REFERENCES, "references"},
118 {MATCHCRITERIA_NOT_REFERENCES, "~references"},
119 {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
120 {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
121 {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
122 {MATCHCRITERIA_PARTIAL, "partial"},
123 {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
124 {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
125 {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
127 {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
128 {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
129 {MATCHCRITERIA_SIZE_EQUAL, "size_equal"},
131 /* content have to be read */
132 {MATCHCRITERIA_HEADER, "header"},
133 {MATCHCRITERIA_NOT_HEADER, "~header"},
134 {MATCHCRITERIA_HEADERS_PART, "headers_part"},
135 {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
136 {MATCHCRITERIA_MESSAGE, "message"},
137 {MATCHCRITERIA_NOT_MESSAGE, "~message"},
138 {MATCHCRITERIA_BODY_PART, "body_part"},
139 {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
140 {MATCHCRITERIA_TEST, "test"},
141 {MATCHCRITERIA_NOT_TEST, "~test"},
144 {MATCHTYPE_MATCHCASE, "matchcase"},
145 {MATCHTYPE_MATCH, "match"},
146 {MATCHTYPE_REGEXPCASE, "regexpcase"},
147 {MATCHTYPE_REGEXP, "regexp"},
150 {MATCHACTION_SCORE, "score"}, /* for backward compatibility */
151 {MATCHACTION_MOVE, "move"},
152 {MATCHACTION_COPY, "copy"},
153 {MATCHACTION_DELETE, "delete"},
154 {MATCHACTION_MARK, "mark"},
155 {MATCHACTION_UNMARK, "unmark"},
156 {MATCHACTION_LOCK, "lock"},
157 {MATCHACTION_UNLOCK, "unlock"},
158 {MATCHACTION_MARK_AS_READ, "mark_as_read"},
159 {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
160 {MATCHACTION_MARK_AS_SPAM, "mark_as_spam"},
161 {MATCHACTION_MARK_AS_HAM, "mark_as_ham"},
162 {MATCHACTION_FORWARD, "forward"},
163 {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
164 {MATCHACTION_EXECUTE, "execute"},
165 {MATCHACTION_COLOR, "color"},
166 {MATCHACTION_REDIRECT, "redirect"},
167 {MATCHACTION_CHANGE_SCORE, "change_score"},
168 {MATCHACTION_SET_SCORE, "set_score"},
169 {MATCHACTION_STOP, "stop"},
170 {MATCHACTION_HIDE, "hide"},
171 {MATCHACTION_IGNORE, "ignore"},
172 {MATCHACTION_WATCH, "watch"},
173 {MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"},
174 {MATCHACTION_SET_TAG, "set_tag"},
175 {MATCHACTION_UNSET_TAG, "unset_tag"},
176 {MATCHACTION_CLEAR_TAGS, "clear_tags"},
185 extern gboolean debug_filtering_session;
188 *\brief Look up table with keywords defined in \sa matchparser_tab
190 static GHashTable *matchparser_hashtab;
193 *\brief Translate keyword id to keyword string
195 *\param id Id of keyword
197 *\return const gchar * Keyword
199 const gchar *get_matchparser_tab_str(gint id)
203 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
204 if (matchparser_tab[i].id == id)
205 return matchparser_tab[i].str;
211 *\brief Create keyword lookup table
213 static void create_matchparser_hashtab(void)
217 if (matchparser_hashtab) return;
218 matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
219 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
220 g_hash_table_insert(matchparser_hashtab,
221 matchparser_tab[i].str,
222 (gpointer) &matchparser_tab[i]);
226 *\brief Return a keyword id from a keyword string
228 *\param str Keyword string
230 *\return gint Keyword id
232 gint get_matchparser_tab_id(const gchar *str)
236 if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
242 /* **************** data structure allocation **************** */
245 *\brief Allocate a structure for a filtering / scoring
246 * "condition" (a matcher structure)
248 *\param criteria Criteria ID (MATCHCRITERIA_XXXX)
249 *\param header Header string (if criteria is MATCHCRITERIA_HEADER
250 or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
251 *\param matchtype Type of action (MATCHTYPE_XXX)
252 *\param expr String value or expression to check
253 *\param value Integer value to check
255 *\return MatcherProp * Pointer to newly allocated structure
257 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
258 gint matchtype, const gchar *expr,
263 prop = g_new0(MatcherProp, 1);
264 prop->criteria = criteria;
265 prop->header = header != NULL ? g_strdup(header) : NULL;
267 prop->expr = expr != NULL ? g_strdup(expr) : NULL;
269 prop->matchtype = matchtype;
278 *\brief Free a matcher structure
280 *\param prop Pointer to matcher structure allocated with
283 void matcherprop_free(MatcherProp *prop)
286 g_free(prop->header);
287 if (prop->preg != NULL) {
295 *\brief Copy a matcher structure
297 *\param src Matcher structure to copy
299 *\return MatcherProp * Pointer to newly allocated matcher structure
301 MatcherProp *matcherprop_copy(const MatcherProp *src)
303 MatcherProp *prop = g_new0(MatcherProp, 1);
305 prop->criteria = src->criteria;
306 prop->header = src->header ? g_strdup(src->header) : NULL;
307 prop->expr = src->expr ? g_strdup(src->expr) : NULL;
308 prop->matchtype = src->matchtype;
310 prop->preg = NULL; /* will be re-evaluated */
311 prop->value = src->value;
312 prop->error = src->error;
316 /* ************** match ******************************/
318 static gboolean match_with_addresses_in_addressbook
319 (MatcherProp *prop, GSList *address_list, gint type,
320 gchar* folderpath, gint match)
323 gboolean found = FALSE;
326 cm_return_val_if_fail(address_list != NULL, FALSE);
328 debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
329 g_slist_length(address_list), folderpath?folderpath:"(null)");
331 if (folderpath == NULL ||
332 strcasecmp(folderpath, "Any") == 0 ||
338 start_address_completion(path);
340 for (walk = address_list; walk != NULL; walk = walk->next) {
341 /* exact matching of email address */
342 guint num_addr = complete_address(walk->data);
345 /* skip first item (this is the search string itself) */
347 for (; i < num_addr && !found; i++) {
348 gchar *addr = get_complete_address(i);
349 extract_address(addr);
350 if (strcasecmp(addr, walk->data) == 0) {
354 if (debug_filtering_session
355 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
356 log_print(LOG_DEBUG_FILTERING,
357 "address [ %s ] matches\n",
358 (gchar *)walk->data);
365 if (debug_filtering_session
366 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH
368 log_print(LOG_DEBUG_FILTERING,
369 "address [ %s ] does NOT match\n",
370 (gchar *)walk->data);
374 if (match == MATCH_ALL) {
375 /* if matching all addresses, stop if one doesn't match */
378 if (debug_filtering_session
379 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
380 log_print(LOG_DEBUG_FILTERING,
381 "not all address match (matching all)\n");
385 } else if (match == MATCH_ANY) {
386 /* if matching any address, stop if one does match */
389 if (debug_filtering_session
390 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
391 log_print(LOG_DEBUG_FILTERING,
392 "at least one address matches (matching any)\n");
397 /* MATCH_ONE: there should be only one loop iteration */
400 end_address_completion();
406 *\brief Find out if a string matches a condition
408 *\param prop Matcher structure
409 *\param str String to check
411 *\return gboolean TRUE if str matches the condition in the
414 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str,
415 const gchar *debug_context)
419 gboolean ret = FALSE;
420 gboolean should_free = FALSE;
424 if (prop->matchtype == MATCHTYPE_REGEXPCASE ||
425 prop->matchtype == MATCHTYPE_MATCHCASE) {
426 str1 = g_utf8_casefold(str, -1);
427 down_expr = g_utf8_casefold(prop->expr, -1);
431 down_expr = (gchar *)prop->expr;
435 switch (prop->matchtype) {
436 case MATCHTYPE_REGEXPCASE:
437 case MATCHTYPE_REGEXP:
439 if (!prop->preg && (prop->error == 0)) {
440 prop->preg = g_new0(regex_t, 1);
441 /* if regexp then don't use the escaped string */
442 if (regcomp(prop->preg, down_expr,
443 REG_NOSUB | REG_EXTENDED
444 | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
445 ? REG_ICASE : 0)) != 0) {
451 if (prop->preg == NULL) {
456 if (regexec(prop->preg, str1, 0, NULL, 0) == 0)
462 if (debug_filtering_session
463 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
464 gchar *stripped = g_strdup(str);
466 strretchomp(stripped);
468 log_print(LOG_DEBUG_FILTERING,
469 "%s value [ %s ] matches regular expression [ %s ] (%s)\n",
470 debug_context, stripped, prop->expr,
471 prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
473 log_print(LOG_DEBUG_FILTERING,
474 "%s value [ %s ] does NOT match regular expression [ %s ] (%s)\n",
475 debug_context, stripped, prop->expr,
476 prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
482 case MATCHTYPE_MATCHCASE:
483 case MATCHTYPE_MATCH:
484 ret = (strstr(str1, down_expr) != NULL);
487 if (debug_filtering_session
488 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
489 gchar *stripped = g_strdup(str);
491 strretchomp(stripped);
493 log_print(LOG_DEBUG_FILTERING,
494 "%s value [ %s ] contains [ %s ] (%s)\n",
495 debug_context, stripped, prop->expr,
496 prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
498 log_print(LOG_DEBUG_FILTERING,
499 "%s value [ %s ] does NOT contain [ %s ] (%s)\n",
500 debug_context, stripped, prop->expr,
501 prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
520 *\brief Find out if a tag matches a condition
522 *\param prop Matcher structure
523 *\param msginfo message to check
525 *\return gboolean TRUE if msginfo matches the condition in the
528 static gboolean matcherprop_tag_match(MatcherProp *prop, MsgInfo *msginfo,
529 const gchar *debug_context)
531 gboolean ret = FALSE;
534 if (msginfo == NULL || msginfo->tags == NULL)
537 for (cur = msginfo->tags; cur; cur = cur->next) {
538 const gchar *str = tags_get_tag(GPOINTER_TO_INT(cur->data));
541 if (matcherprop_string_match(prop, str, debug_context)) {
550 *\brief Find out if the string-ed list matches a condition
552 *\param prop Matcher structure
553 *\param list GSList of strings to check
555 *\return gboolean TRUE if str matches the condition in the
558 static gboolean matcherprop_list_match(MatcherProp *prop, const GSList *list,
559 const gchar *debug_context)
563 for(cur = list; cur != NULL; cur = cur->next) {
564 if (matcherprop_string_match(prop, (gchar *)cur->data, debug_context))
570 static gboolean matcherprop_header_line_match(MatcherProp *prop, const gchar *hdr,
571 const gchar *str, const gchar *debug_context)
574 gboolean res = FALSE;
576 if (hdr == NULL || str == NULL)
579 line = g_strdup_printf("%s %s", hdr, str);
580 res = matcherprop_string_match(prop, line, debug_context);
587 typedef struct _thread_data {
594 static void *matcher_test_thread(void *data)
596 thread_data *td = (thread_data *)data;
599 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
600 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
602 result = system(td->cmd);
603 td->done = TRUE; /* let the caller thread join() */
604 return GINT_TO_POINTER(result);
609 *\brief Execute a command defined in the matcher structure
611 *\param prop Pointer to matcher structure
612 *\param info Pointer to message info structure
614 *\return gboolean TRUE if command was executed succesfully
616 static gboolean matcherprop_match_test(const MatcherProp *prop,
625 thread_data *td = g_new0(thread_data, 1);
627 time_t start_time = time(NULL);
630 file = procmsg_get_message_file(info);
639 cmd = matching_build_command(prop->expr, info);
649 if (debug_filtering_session
650 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
651 log_print(LOG_DEBUG_FILTERING,
652 "starting threaded command [ %s ]\n",
658 if (pthread_attr_init(&pta) != 0 ||
659 pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_JOINABLE) != 0 ||
660 pthread_create(&pt, &pta, matcher_test_thread, td) != 0)
661 retval = system(cmd);
663 debug_print("waiting for test thread\n");
665 /* don't let the interface freeze while waiting */
667 if (time(NULL) - start_time > 30) {
673 pthread_join(pt, &res);
674 retval = GPOINTER_TO_INT(res);
675 debug_print(" test thread returned %d\n", retval);
680 if (debug_filtering_session
681 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
682 log_print(LOG_DEBUG_FILTERING,
683 "starting synchronous command [ %s ]\n",
687 retval = system(cmd);
689 debug_print("Command exit code: %d\n", retval);
692 if (debug_filtering_session
693 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
694 log_print(LOG_DEBUG_FILTERING,
695 "command returned [ %d ]\n",
700 return (retval == 0);
704 *\brief Check if a message matches the condition in a matcher
707 *\param prop Pointer to matcher structure
708 *\param info Pointer to message info
710 *\return gboolean TRUE if a match
712 static gboolean matcherprop_match(MatcherProp *prop,
717 switch(prop->criteria) {
718 case MATCHCRITERIA_ALL:
720 case MATCHCRITERIA_UNREAD:
721 return MSG_IS_UNREAD(info->flags);
722 case MATCHCRITERIA_NOT_UNREAD:
723 return !MSG_IS_UNREAD(info->flags);
724 case MATCHCRITERIA_NEW:
725 return MSG_IS_NEW(info->flags);
726 case MATCHCRITERIA_NOT_NEW:
727 return !MSG_IS_NEW(info->flags);
728 case MATCHCRITERIA_MARKED:
729 return MSG_IS_MARKED(info->flags);
730 case MATCHCRITERIA_NOT_MARKED:
731 return !MSG_IS_MARKED(info->flags);
732 case MATCHCRITERIA_DELETED:
733 return MSG_IS_DELETED(info->flags);
734 case MATCHCRITERIA_NOT_DELETED:
735 return !MSG_IS_DELETED(info->flags);
736 case MATCHCRITERIA_REPLIED:
737 return MSG_IS_REPLIED(info->flags);
738 case MATCHCRITERIA_NOT_REPLIED:
739 return !MSG_IS_REPLIED(info->flags);
740 case MATCHCRITERIA_FORWARDED:
741 return MSG_IS_FORWARDED(info->flags);
742 case MATCHCRITERIA_NOT_FORWARDED:
743 return !MSG_IS_FORWARDED(info->flags);
744 case MATCHCRITERIA_LOCKED:
745 return MSG_IS_LOCKED(info->flags);
746 case MATCHCRITERIA_NOT_LOCKED:
747 return !MSG_IS_LOCKED(info->flags);
748 case MATCHCRITERIA_SPAM:
749 return MSG_IS_SPAM(info->flags);
750 case MATCHCRITERIA_NOT_SPAM:
751 return !MSG_IS_SPAM(info->flags);
752 case MATCHCRITERIA_HAS_ATTACHMENT:
753 return MSG_IS_WITH_ATTACHMENT(info->flags);
754 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
755 return !MSG_IS_WITH_ATTACHMENT(info->flags);
756 case MATCHCRITERIA_SIGNED:
757 return MSG_IS_SIGNED(info->flags);
758 case MATCHCRITERIA_NOT_SIGNED:
759 return !MSG_IS_SIGNED(info->flags);
760 case MATCHCRITERIA_COLORLABEL:
762 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
763 gboolean ret = (color == prop->value);
766 if (debug_filtering_session
767 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
769 log_print(LOG_DEBUG_FILTERING,
770 "message color value [ %d ] matches color value [ %d ]\n",
773 log_print(LOG_DEBUG_FILTERING,
774 "message color value [ %d ] does NOT match color value [ %d ]\n",
780 case MATCHCRITERIA_NOT_COLORLABEL:
782 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
783 gboolean ret = (color != prop->value);
786 if (debug_filtering_session
787 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
789 log_print(LOG_DEBUG_FILTERING,
790 "message color value [ %d ] matches color value [ %d ]\n",
793 log_print(LOG_DEBUG_FILTERING,
794 "message color value [ %d ] does NOT match color value [ %d ]\n",
800 case MATCHCRITERIA_IGNORE_THREAD:
801 return MSG_IS_IGNORE_THREAD(info->flags);
802 case MATCHCRITERIA_NOT_IGNORE_THREAD:
803 return !MSG_IS_IGNORE_THREAD(info->flags);
804 case MATCHCRITERIA_WATCH_THREAD:
805 return MSG_IS_WATCH_THREAD(info->flags);
806 case MATCHCRITERIA_NOT_WATCH_THREAD:
807 return !MSG_IS_WATCH_THREAD(info->flags);
808 case MATCHCRITERIA_SUBJECT:
809 return matcherprop_string_match(prop, info->subject,
810 prefs_common_translated_header_name("Subject:"));
811 case MATCHCRITERIA_NOT_SUBJECT:
812 return !matcherprop_string_match(prop, info->subject,
813 prefs_common_translated_header_name("Subject:"));
814 case MATCHCRITERIA_FROM:
815 case MATCHCRITERIA_NOT_FROM:
820 context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
821 ret = matcherprop_string_match(prop, info->from, context);
823 return (prop->criteria == MATCHCRITERIA_FROM)? ret : !ret;
825 case MATCHCRITERIA_TO:
826 case MATCHCRITERIA_NOT_TO:
831 context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
832 ret = matcherprop_string_match(prop, info->to, context);
834 return (prop->criteria == MATCHCRITERIA_TO)? ret : !ret;
836 case MATCHCRITERIA_CC:
837 case MATCHCRITERIA_NOT_CC:
842 context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
843 ret = matcherprop_string_match(prop, info->cc, context);
845 return (prop->criteria == MATCHCRITERIA_CC)? ret : !ret;
847 case MATCHCRITERIA_TO_OR_CC:
849 gchar *context1, *context2;
852 context1 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
853 context2 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
854 ret = matcherprop_string_match(prop, info->to, context1)
855 || matcherprop_string_match(prop, info->cc, context2);
860 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
862 gchar *context1, *context2;
865 context1 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
866 context2 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
867 ret = !(matcherprop_string_match(prop, info->to, context1)
868 || matcherprop_string_match(prop, info->cc, context2));
873 case MATCHCRITERIA_TAG:
874 case MATCHCRITERIA_NOT_TAG:
878 ret = matcherprop_tag_match(prop, info, _("Tag"));
879 return (prop->criteria == MATCHCRITERIA_TAG)? ret : !ret;
881 case MATCHCRITERIA_TAGGED:
882 case MATCHCRITERIA_NOT_TAGGED:
886 ret = (info->tags != NULL);
887 return (prop->criteria == MATCHCRITERIA_TAGGED)? ret : !ret;
889 case MATCHCRITERIA_AGE_GREATER:
895 age = ((t - info->date_t) / (60 * 60 * 24));
896 ret = (age >= prop->value);
899 if (debug_filtering_session
900 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
902 log_print(LOG_DEBUG_FILTERING,
903 "message age [ %d ] is greater than [ %d ]\n",
906 log_print(LOG_DEBUG_FILTERING,
907 "message age [ %d ] is not greater than [ %d ]\n",
913 case MATCHCRITERIA_AGE_LOWER:
919 age = ((t - info->date_t) / (60 * 60 * 24));
920 ret = (age < prop->value);
923 if (debug_filtering_session
924 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
926 log_print(LOG_DEBUG_FILTERING,
927 "message age [ %d ] is lower than [ %d ]\n",
930 log_print(LOG_DEBUG_FILTERING,
931 "message age [ %d ] is not lower than [ %d ]\n",
937 case MATCHCRITERIA_SCORE_GREATER:
939 gboolean ret = (info->score > prop->value);
942 if (debug_filtering_session
943 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
945 log_print(LOG_DEBUG_FILTERING,
946 "message score [ %d ] is greater than [ %d ]\n",
947 info->score, prop->value);
949 log_print(LOG_DEBUG_FILTERING,
950 "message score [ %d ] is not greater than [ %d ]\n",
951 info->score, prop->value);
956 case MATCHCRITERIA_SCORE_LOWER:
958 gboolean ret = (info->score < prop->value);
961 if (debug_filtering_session
962 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
964 log_print(LOG_DEBUG_FILTERING,
965 "message score [ %d ] is lower than [ %d ]\n",
966 info->score, prop->value);
968 log_print(LOG_DEBUG_FILTERING,
969 "message score [ %d ] is not lower than [ %d ]\n",
970 info->score, prop->value);
975 case MATCHCRITERIA_SCORE_EQUAL:
977 gboolean ret = (info->score == prop->value);
980 if (debug_filtering_session
981 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
983 log_print(LOG_DEBUG_FILTERING,
984 "message score [ %d ] is equal to [ %d ]\n",
985 info->score, prop->value);
987 log_print(LOG_DEBUG_FILTERING,
988 "message score [ %d ] is not equal to [ %d ]\n",
989 info->score, prop->value);
994 case MATCHCRITERIA_SIZE_GREATER:
996 /* FIXME: info->size is a goffset */
997 gboolean ret = (info->size > (goffset) prop->value);
1000 if (debug_filtering_session
1001 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1003 log_print(LOG_DEBUG_FILTERING,
1004 "message size is greater than [ %d ]\n",
1007 log_print(LOG_DEBUG_FILTERING,
1008 "message size is not greater than [ %d ]\n",
1014 case MATCHCRITERIA_SIZE_SMALLER:
1016 /* FIXME: info->size is a goffset */
1017 gboolean ret = (info->size < (goffset) prop->value);
1020 if (debug_filtering_session
1021 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1023 log_print(LOG_DEBUG_FILTERING,
1024 "message size is smaller than [ %d ]\n",
1027 log_print(LOG_DEBUG_FILTERING,
1028 "message size is not smaller than [ %d ]\n",
1034 case MATCHCRITERIA_SIZE_EQUAL:
1036 /* FIXME: info->size is a goffset */
1037 gboolean ret = (info->size == (goffset) prop->value);
1040 if (debug_filtering_session
1041 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1043 log_print(LOG_DEBUG_FILTERING,
1044 "message size is equal to [ %d ]\n",
1047 log_print(LOG_DEBUG_FILTERING,
1048 "message size is not equal to [ %d ]\n",
1054 case MATCHCRITERIA_PARTIAL:
1056 /* FIXME: info->size is a goffset */
1057 gboolean ret = (info->total_size != 0 && info->size != (goffset)info->total_size);
1060 if (debug_filtering_session
1061 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1063 log_print(LOG_DEBUG_FILTERING,
1064 "message is partially downloaded, size is less than total size [ %d ])\n",
1067 log_print(LOG_DEBUG_FILTERING,
1068 "message is not partially downloaded\n");
1073 case MATCHCRITERIA_NOT_PARTIAL:
1075 /* FIXME: info->size is a goffset */
1076 gboolean ret = (info->total_size == 0 || info->size == (goffset)info->total_size);
1079 if (debug_filtering_session
1080 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1082 log_print(LOG_DEBUG_FILTERING,
1083 "message is not partially downloaded\n");
1085 log_print(LOG_DEBUG_FILTERING,
1086 "message is partially downloaded, size is less than total size [ %d ])\n",
1092 case MATCHCRITERIA_NEWSGROUPS:
1093 case MATCHCRITERIA_NOT_NEWSGROUPS:
1098 context = g_strdup_printf(_("%s header"),
1099 prefs_common_translated_header_name("Newsgroups:"));
1100 ret = matcherprop_string_match(prop, info->newsgroups, context);
1102 return (prop->criteria == MATCHCRITERIA_NEWSGROUPS)? ret : !ret;
1104 case MATCHCRITERIA_INREPLYTO:
1105 case MATCHCRITERIA_NOT_INREPLYTO:
1110 context = g_strdup_printf(_("%s header"),
1111 prefs_common_translated_header_name("In-Reply-To:"));
1112 ret = matcherprop_string_match(prop, info->inreplyto, context);
1114 return (prop->criteria == MATCHCRITERIA_INREPLYTO)? ret : !ret;
1116 case MATCHCRITERIA_REFERENCES:
1117 case MATCHCRITERIA_NOT_REFERENCES:
1122 context = g_strdup_printf(_("%s header"),
1123 prefs_common_translated_header_name("References:"));
1124 ret = matcherprop_list_match(prop, info->references, context);
1126 return (prop->criteria == MATCHCRITERIA_REFERENCES)? ret : !ret;
1128 case MATCHCRITERIA_TEST:
1129 return matcherprop_match_test(prop, info);
1130 case MATCHCRITERIA_NOT_TEST:
1131 return !matcherprop_match_test(prop, info);
1137 /* ********************* MatcherList *************************** */
1140 *\brief Create a new list of matchers
1142 *\param matchers List of matcher structures
1143 *\param bool_and Operator
1145 *\return MatcherList * New list
1147 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
1151 cond = g_new0(MatcherList, 1);
1153 cond->matchers = matchers;
1154 cond->bool_and = bool_and;
1160 *\brief Frees a list of matchers
1162 *\param cond List of matchers
1164 void matcherlist_free(MatcherList *cond)
1168 cm_return_if_fail(cond);
1169 for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
1170 matcherprop_free((MatcherProp *) l->data);
1172 g_slist_free(cond->matchers);
1177 *\brief Skip all headers in a message file
1179 *\param fp Message file
1181 static void matcherlist_skip_headers(FILE *fp)
1183 gchar buf[BUFFSIZE];
1185 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
1190 *\brief Check if a header matches a matcher condition
1192 *\param matcher Matcher structure to check header for
1193 *\param buf Header name
1195 *\return boolean TRUE if matching header
1197 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
1200 gboolean result = FALSE;
1201 Header *header = NULL;
1203 switch (matcher->criteria) {
1204 case MATCHCRITERIA_HEADER:
1205 case MATCHCRITERIA_NOT_HEADER:
1206 header = procheader_parse_header(buf);
1209 if (procheader_headername_equal(header->name,
1211 if (matcher->criteria == MATCHCRITERIA_HEADER)
1212 result = matcherprop_string_match(matcher, header->body, _("header"));
1214 result = !matcherprop_string_match(matcher, header->body, _("header"));
1215 procheader_header_free(header);
1219 procheader_header_free(header);
1222 case MATCHCRITERIA_HEADERS_PART:
1223 case MATCHCRITERIA_MESSAGE:
1224 header = procheader_parse_header(buf);
1227 result = matcherprop_header_line_match(matcher,
1228 header->name, header->body, _("header line"));
1229 procheader_header_free(header);
1231 case MATCHCRITERIA_NOT_HEADERS_PART:
1232 case MATCHCRITERIA_NOT_MESSAGE:
1233 header = procheader_parse_header(buf);
1236 result = !matcherprop_header_line_match(matcher,
1237 header->name, header->body, _("header line"));
1238 procheader_header_free(header);
1240 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1241 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1243 GSList *address_list = NULL;
1244 gint match = MATCH_ONE;
1245 gboolean found = FALSE;
1247 /* how many address headers are me trying to mach? */
1248 if (strcasecmp(matcher->header, "Any") == 0)
1250 else if (strcasecmp(matcher->header, "All") == 0)
1253 if (match == MATCH_ONE) {
1254 /* matching one address header exactly, is that the right one? */
1255 header = procheader_parse_header(buf);
1257 !procheader_headername_equal(header->name, matcher->header))
1259 address_list = address_list_append(address_list, header->body);
1260 if (address_list == NULL)
1264 header = procheader_parse_header(buf);
1267 /* address header is one of the headers we have to match when checking
1268 for any address header or all address headers? */
1269 if (procheader_headername_equal(header->name, "From") ||
1270 procheader_headername_equal(header->name, "To") ||
1271 procheader_headername_equal(header->name, "Cc") ||
1272 procheader_headername_equal(header->name, "Reply-To") ||
1273 procheader_headername_equal(header->name, "Sender"))
1274 address_list = address_list_append(address_list, header->body);
1275 if (address_list == NULL)
1279 found = match_with_addresses_in_addressbook
1280 (matcher, address_list, matcher->criteria,
1281 matcher->expr, match);
1282 g_slist_free(address_list);
1284 if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
1295 *\brief Check if the matcher structure wants headers to
1298 *\param matcher Matcher structure
1300 *\return gboolean TRUE if the matcher structure describes
1301 * a header match condition
1303 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
1305 switch (matcher->criteria) {
1306 case MATCHCRITERIA_HEADER:
1307 case MATCHCRITERIA_NOT_HEADER:
1308 case MATCHCRITERIA_HEADERS_PART:
1309 case MATCHCRITERIA_NOT_HEADERS_PART:
1310 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1311 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1319 *\brief Check if the matcher structure wants the message
1320 * to be matched (just perform an action on any
1323 *\param matcher Matcher structure
1325 *\return gboolean TRUE if matcher condition should match
1328 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
1330 switch (matcher->criteria) {
1331 case MATCHCRITERIA_MESSAGE:
1332 case MATCHCRITERIA_NOT_MESSAGE:
1340 *\brief Check if a list of conditions matches one header in
1343 *\param matchers List of conditions
1344 *\param fp Message file
1346 *\return gboolean TRUE if one of the headers is matched by
1347 * the list of conditions.
1349 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
1352 gchar buf[BUFFSIZE];
1354 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
1355 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1356 MatcherProp *matcher = (MatcherProp *) l->data;
1357 gint match = MATCH_ANY;
1362 /* determine the match range (all, any are our concern here) */
1363 if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
1364 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1367 } else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
1368 matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
1369 Header *header = NULL;
1371 /* address header is one of the headers we have to match when checking
1372 for any address header or all address headers? */
1373 header = procheader_parse_header(buf);
1375 (procheader_headername_equal(header->name, "From") ||
1376 procheader_headername_equal(header->name, "To") ||
1377 procheader_headername_equal(header->name, "Cc") ||
1378 procheader_headername_equal(header->name, "Reply-To") ||
1379 procheader_headername_equal(header->name, "Sender"))) {
1381 if (strcasecmp(matcher->header, "Any") == 0)
1383 else if (strcasecmp(matcher->header, "All") == 0)
1388 /* further call to matcherprop_match_one_header() can't match
1389 and it irrelevant, so: don't alter the match result */
1394 /* ZERO line must NOT match for the rule to match.
1396 if (match == MATCH_ALL) {
1397 if (matcherprop_match_one_header(matcher, buf)) {
1398 matcher->result = TRUE;
1400 matcher->result = FALSE;
1401 matcher->done = TRUE;
1403 /* else, just one line matching is enough for the rule to match
1405 } else if (matcherprop_criteria_headers(matcher) ||
1406 matcherprop_criteria_message(matcher)) {
1407 if (matcherprop_match_one_header(matcher, buf)) {
1408 matcher->result = TRUE;
1409 matcher->done = TRUE;
1413 /* if the rule matched and the matchers are OR, no need to
1414 * check the others */
1415 if (matcher->result && matcher->done) {
1416 if (!matchers->bool_and)
1426 *\brief Check if a matcher wants to check the message body
1428 *\param matcher Matcher structure
1430 *\return gboolean TRUE if body must be matched.
1432 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
1434 switch (matcher->criteria) {
1435 case MATCHCRITERIA_BODY_PART:
1436 case MATCHCRITERIA_NOT_BODY_PART:
1443 static gboolean matcherlist_match_binary_content(MatcherList *matchers, MimeInfo *partinfo)
1446 gchar buf[BUFFSIZE];
1449 if (partinfo->type == MIMETYPE_TEXT)
1452 outfp = procmime_get_binary_content(partinfo);
1457 while (fgets(buf, sizeof(buf), outfp) != NULL) {
1460 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1461 MatcherProp *matcher = (MatcherProp *) l->data;
1466 /* Don't scan non-text parts when looking in body, only
1467 * when looking in whole message
1469 if (partinfo && partinfo->type != MIMETYPE_TEXT &&
1470 (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1471 matcher->criteria == MATCHCRITERIA_BODY_PART))
1474 /* if the criteria is ~body_part or ~message, ZERO lines
1475 * must match for the rule to match.
1477 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1478 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1479 if (matcherprop_string_match(matcher, buf,
1481 matcher->result = FALSE;
1482 matcher->done = TRUE;
1484 matcher->result = TRUE;
1485 /* else, just one line has to match */
1486 } else if (matcherprop_criteria_body(matcher) ||
1487 matcherprop_criteria_message(matcher)) {
1488 if (matcherprop_string_match(matcher, buf,
1490 matcher->result = TRUE;
1491 matcher->done = TRUE;
1495 /* if the matchers are OR'ed and the rule matched,
1496 * no need to check the others. */
1497 if (matcher->result && matcher->done) {
1498 if (!matchers->bool_and) {
1510 static gboolean match_content_cb(const gchar *buf, gpointer data)
1512 MatcherList *matchers = (MatcherList *)data;
1513 gboolean all_done = TRUE;
1516 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1517 MatcherProp *matcher = (MatcherProp *) l->data;
1522 /* if the criteria is ~body_part or ~message, ZERO lines
1523 * must match for the rule to match.
1525 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1526 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1527 if (matcherprop_string_match(matcher, buf,
1529 matcher->result = FALSE;
1530 matcher->done = TRUE;
1532 matcher->result = TRUE;
1533 /* else, just one line has to match */
1534 } else if (matcherprop_criteria_body(matcher) ||
1535 matcherprop_criteria_message(matcher)) {
1536 if (matcherprop_string_match(matcher, buf,
1538 matcher->result = TRUE;
1539 matcher->done = TRUE;
1543 /* if the matchers are OR'ed and the rule matched,
1544 * no need to check the others. */
1545 if (matcher->result && matcher->done) {
1546 if (!matchers->bool_and) {
1557 static gboolean matcherlist_match_text_content(MatcherList *matchers, MimeInfo *partinfo)
1559 if (partinfo->type != MIMETYPE_TEXT)
1562 return procmime_scan_text_content(partinfo, match_content_cb, matchers);
1566 *\brief Check if a line in a message file's body matches
1569 *\param matchers List of conditions
1570 *\param fp Message file
1572 *\return gboolean TRUE if succesful match
1574 static gboolean matcherlist_match_body(MatcherList *matchers, gboolean body_only, MsgInfo *info)
1576 MimeInfo *mimeinfo = NULL;
1577 MimeInfo *partinfo = NULL;
1578 gboolean first_text_found = FALSE;
1580 cm_return_val_if_fail(info != NULL, FALSE);
1582 mimeinfo = procmime_scan_message(info);
1585 partinfo = procmime_mimeinfo_next(mimeinfo);
1587 for (; partinfo != NULL; partinfo = procmime_mimeinfo_next(partinfo)) {
1589 if (partinfo->type != MIMETYPE_TEXT && body_only)
1592 if (partinfo->type == MIMETYPE_TEXT) {
1593 first_text_found = TRUE;
1594 if (matcherlist_match_text_content(matchers, partinfo)) {
1595 procmime_mimeinfo_free_all(mimeinfo);
1598 } else if (matcherlist_match_binary_content(matchers, partinfo)) {
1599 procmime_mimeinfo_free_all(mimeinfo);
1603 if (body_only && first_text_found)
1606 procmime_mimeinfo_free_all(mimeinfo);
1612 *\brief Check if a message file matches criteria
1614 *\param matchers Criteria
1615 *\param info Message info
1616 *\param result Default result
1618 *\return gboolean TRUE if matched
1620 static gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
1623 gboolean read_headers;
1630 /* file need to be read ? */
1632 read_headers = FALSE;
1635 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1636 MatcherProp *matcher = (MatcherProp *) l->data;
1638 if (matcherprop_criteria_headers(matcher))
1639 read_headers = TRUE;
1640 if (matcherprop_criteria_body(matcher))
1642 if (matcherprop_criteria_message(matcher)) {
1643 read_headers = TRUE;
1647 matcher->result = FALSE;
1648 matcher->done = FALSE;
1651 if (!read_headers && !read_body)
1654 file = procmsg_get_message_file_full(info, read_headers, read_body);
1658 if ((fp = g_fopen(file, "rb")) == NULL) {
1659 FILE_OP_ERROR(file, "fopen");
1664 /* read the headers */
1667 if (matcherlist_match_headers(matchers, fp))
1670 matcherlist_skip_headers(fp);
1675 matcherlist_match_body(matchers, body_only, info);
1678 for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
1679 MatcherProp *matcher = (MatcherProp *) l->data;
1681 if (matcherprop_criteria_headers(matcher) ||
1682 matcherprop_criteria_body(matcher) ||
1683 matcherprop_criteria_message(matcher)) {
1684 if (matcher->result) {
1685 if (!matchers->bool_and) {
1691 if (matchers->bool_and) {
1707 *\brief Test list of conditions on a message.
1709 *\param matchers List of conditions
1710 *\param info Message info
1712 *\return gboolean TRUE if matched
1714 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
1722 if (matchers->bool_and)
1727 /* test the cached elements */
1729 for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
1730 MatcherProp *matcher = (MatcherProp *) l->data;
1732 if (debug_filtering_session) {
1733 gchar *buf = matcherprop_to_string(matcher);
1734 log_print(LOG_DEBUG_FILTERING, _("checking if message matches [ %s ]\n"), buf);
1738 switch(matcher->criteria) {
1739 case MATCHCRITERIA_ALL:
1740 case MATCHCRITERIA_UNREAD:
1741 case MATCHCRITERIA_NOT_UNREAD:
1742 case MATCHCRITERIA_NEW:
1743 case MATCHCRITERIA_NOT_NEW:
1744 case MATCHCRITERIA_MARKED:
1745 case MATCHCRITERIA_NOT_MARKED:
1746 case MATCHCRITERIA_DELETED:
1747 case MATCHCRITERIA_NOT_DELETED:
1748 case MATCHCRITERIA_REPLIED:
1749 case MATCHCRITERIA_NOT_REPLIED:
1750 case MATCHCRITERIA_FORWARDED:
1751 case MATCHCRITERIA_NOT_FORWARDED:
1752 case MATCHCRITERIA_LOCKED:
1753 case MATCHCRITERIA_NOT_LOCKED:
1754 case MATCHCRITERIA_SPAM:
1755 case MATCHCRITERIA_NOT_SPAM:
1756 case MATCHCRITERIA_HAS_ATTACHMENT:
1757 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
1758 case MATCHCRITERIA_SIGNED:
1759 case MATCHCRITERIA_NOT_SIGNED:
1760 case MATCHCRITERIA_COLORLABEL:
1761 case MATCHCRITERIA_NOT_COLORLABEL:
1762 case MATCHCRITERIA_IGNORE_THREAD:
1763 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1764 case MATCHCRITERIA_WATCH_THREAD:
1765 case MATCHCRITERIA_NOT_WATCH_THREAD:
1766 case MATCHCRITERIA_SUBJECT:
1767 case MATCHCRITERIA_NOT_SUBJECT:
1768 case MATCHCRITERIA_FROM:
1769 case MATCHCRITERIA_NOT_FROM:
1770 case MATCHCRITERIA_TO:
1771 case MATCHCRITERIA_NOT_TO:
1772 case MATCHCRITERIA_CC:
1773 case MATCHCRITERIA_NOT_CC:
1774 case MATCHCRITERIA_TO_OR_CC:
1775 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1776 case MATCHCRITERIA_TAG:
1777 case MATCHCRITERIA_NOT_TAG:
1778 case MATCHCRITERIA_TAGGED:
1779 case MATCHCRITERIA_NOT_TAGGED:
1780 case MATCHCRITERIA_AGE_GREATER:
1781 case MATCHCRITERIA_AGE_LOWER:
1782 case MATCHCRITERIA_NEWSGROUPS:
1783 case MATCHCRITERIA_NOT_NEWSGROUPS:
1784 case MATCHCRITERIA_INREPLYTO:
1785 case MATCHCRITERIA_NOT_INREPLYTO:
1786 case MATCHCRITERIA_REFERENCES:
1787 case MATCHCRITERIA_NOT_REFERENCES:
1788 case MATCHCRITERIA_SCORE_GREATER:
1789 case MATCHCRITERIA_SCORE_LOWER:
1790 case MATCHCRITERIA_SCORE_EQUAL:
1791 case MATCHCRITERIA_SIZE_GREATER:
1792 case MATCHCRITERIA_SIZE_SMALLER:
1793 case MATCHCRITERIA_SIZE_EQUAL:
1794 case MATCHCRITERIA_TEST:
1795 case MATCHCRITERIA_NOT_TEST:
1796 case MATCHCRITERIA_PARTIAL:
1797 case MATCHCRITERIA_NOT_PARTIAL:
1798 if (matcherprop_match(matcher, info)) {
1799 if (!matchers->bool_and) {
1800 if (debug_filtering_session)
1801 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1806 if (matchers->bool_and) {
1807 if (debug_filtering_session)
1808 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1815 /* test the condition on the file */
1817 if (matcherlist_match_file(matchers, info, result)) {
1818 if (!matchers->bool_and) {
1819 if (debug_filtering_session)
1820 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1824 if (matchers->bool_and) {
1825 if (debug_filtering_session)
1826 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1831 if (debug_filtering_session) {
1833 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1835 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1841 static gint quote_filter_str(gchar * result, guint size,
1851 for(p = path ; * p != '\0' ; p ++) {
1853 if ((* p != '\"') && (* p != '\\')) {
1854 if (remaining > 0) {
1860 result[size - 1] = '\0';
1865 if (remaining >= 2) {
1873 result[size - 1] = '\0';
1878 if (remaining > 0) {
1882 result[size - 1] = '\0';
1890 gchar * matcher_quote_str(const gchar * src)
1895 len = strlen(src) * 2 + 1;
1896 res = g_malloc(len);
1897 quote_filter_str(res, len, src);
1903 *\brief Convert a matcher structure to a string
1905 *\param matcher Matcher structure
1907 *\return gchar * Newly allocated string
1909 gchar *matcherprop_to_string(MatcherProp *matcher)
1911 gchar *matcher_str = NULL;
1912 const gchar *criteria_str;
1913 const gchar *matchtype_str;
1915 gchar * quoted_expr;
1916 gchar * quoted_header;
1918 criteria_str = NULL;
1919 for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
1920 if (matchparser_tab[i].id == matcher->criteria)
1921 criteria_str = matchparser_tab[i].str;
1923 if (criteria_str == NULL)
1926 switch (matcher->criteria) {
1927 case MATCHCRITERIA_AGE_GREATER:
1928 case MATCHCRITERIA_AGE_LOWER:
1929 case MATCHCRITERIA_SCORE_GREATER:
1930 case MATCHCRITERIA_SCORE_LOWER:
1931 case MATCHCRITERIA_SCORE_EQUAL:
1932 case MATCHCRITERIA_SIZE_GREATER:
1933 case MATCHCRITERIA_SIZE_SMALLER:
1934 case MATCHCRITERIA_SIZE_EQUAL:
1935 case MATCHCRITERIA_COLORLABEL:
1936 case MATCHCRITERIA_NOT_COLORLABEL:
1937 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1938 case MATCHCRITERIA_ALL:
1939 case MATCHCRITERIA_UNREAD:
1940 case MATCHCRITERIA_NOT_UNREAD:
1941 case MATCHCRITERIA_NEW:
1942 case MATCHCRITERIA_NOT_NEW:
1943 case MATCHCRITERIA_MARKED:
1944 case MATCHCRITERIA_NOT_MARKED:
1945 case MATCHCRITERIA_DELETED:
1946 case MATCHCRITERIA_NOT_DELETED:
1947 case MATCHCRITERIA_REPLIED:
1948 case MATCHCRITERIA_NOT_REPLIED:
1949 case MATCHCRITERIA_FORWARDED:
1950 case MATCHCRITERIA_NOT_FORWARDED:
1951 case MATCHCRITERIA_LOCKED:
1952 case MATCHCRITERIA_NOT_LOCKED:
1953 case MATCHCRITERIA_SPAM:
1954 case MATCHCRITERIA_NOT_SPAM:
1955 case MATCHCRITERIA_HAS_ATTACHMENT:
1956 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
1957 case MATCHCRITERIA_SIGNED:
1958 case MATCHCRITERIA_NOT_SIGNED:
1959 case MATCHCRITERIA_PARTIAL:
1960 case MATCHCRITERIA_NOT_PARTIAL:
1961 case MATCHCRITERIA_IGNORE_THREAD:
1962 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1963 case MATCHCRITERIA_WATCH_THREAD:
1964 case MATCHCRITERIA_NOT_WATCH_THREAD:
1965 case MATCHCRITERIA_TAGGED:
1966 case MATCHCRITERIA_NOT_TAGGED:
1967 return g_strdup(criteria_str);
1968 case MATCHCRITERIA_TEST:
1969 case MATCHCRITERIA_NOT_TEST:
1970 quoted_expr = matcher_quote_str(matcher->expr);
1971 matcher_str = g_strdup_printf("%s \"%s\"",
1972 criteria_str, quoted_expr);
1973 g_free(quoted_expr);
1975 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1976 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1977 quoted_header = matcher_quote_str(matcher->header);
1978 quoted_expr = matcher_quote_str(matcher->expr);
1979 matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
1980 criteria_str, quoted_header, quoted_expr);
1981 g_free(quoted_header);
1982 g_free(quoted_expr);
1986 matchtype_str = NULL;
1987 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
1988 if (matchparser_tab[i].id == matcher->matchtype)
1989 matchtype_str = matchparser_tab[i].str;
1992 if (matchtype_str == NULL)
1995 switch (matcher->matchtype) {
1996 case MATCHTYPE_MATCH:
1997 case MATCHTYPE_MATCHCASE:
1998 case MATCHTYPE_REGEXP:
1999 case MATCHTYPE_REGEXPCASE:
2000 quoted_expr = matcher_quote_str(matcher->expr);
2001 if (matcher->header) {
2002 quoted_header = matcher_quote_str(matcher->header);
2003 matcher_str = g_strdup_printf
2004 ("%s \"%s\" %s \"%s\"",
2005 criteria_str, quoted_header,
2006 matchtype_str, quoted_expr);
2007 g_free(quoted_header);
2010 matcher_str = g_strdup_printf
2011 ("%s %s \"%s\"", criteria_str,
2012 matchtype_str, quoted_expr);
2013 g_free(quoted_expr);
2021 *\brief Convert a list of conditions to a string
2023 *\param matchers List of conditions
2025 *\return gchar * Newly allocated string
2027 gchar *matcherlist_to_string(const MatcherList *matchers)
2033 gchar *result = NULL;
2035 count = g_slist_length(matchers->matchers);
2036 vstr = g_new(gchar *, count + 1);
2038 for (l = matchers->matchers, cur_str = vstr; l != NULL;
2039 l = g_slist_next(l), cur_str ++) {
2040 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
2041 if (*cur_str == NULL)
2046 if (matchers->bool_and)
2047 result = g_strjoinv(" & ", vstr);
2049 result = g_strjoinv(" | ", vstr);
2051 for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
2059 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
2060 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
2062 static void add_str_default(gchar ** dest,
2063 const gchar * s, const gchar * d)
2065 gchar quoted_str[4096];
2073 quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
2074 strcpy(* dest, quoted_str);
2076 (* dest) += strlen(* dest);
2079 /* matching_build_command() - preferably cmd should be unescaped */
2081 *\brief Build the command-line to execute
2083 *\param cmd String with command-line specifiers
2084 *\param info Message info to use for command
2086 *\return gchar * Newly allocated string
2088 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
2090 const gchar *s = cmd;
2091 gchar *filename = NULL;
2092 gchar *processed_cmd;
2096 const gchar *const no_subject = _("(none)") ;
2097 const gchar *const no_from = _("(none)") ;
2098 const gchar *const no_to = _("(none)") ;
2099 const gchar *const no_cc = _("(none)") ;
2100 const gchar *const no_date = _("(none)") ;
2101 const gchar *const no_msgid = _("(none)") ;
2102 const gchar *const no_newsgroups = _("(none)") ;
2103 const gchar *const no_references = _("(none)") ;
2105 size = STRLEN_ZERO(cmd) + 1;
2106 while (*s != '\0') {
2113 case 's': /* subject */
2114 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
2116 case 'f': /* from */
2117 size += STRLEN_DEFAULT(info->from, no_from) - 2;
2120 size += STRLEN_DEFAULT(info->to, no_to) - 2;
2123 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
2125 case 'd': /* date */
2126 size += STRLEN_DEFAULT(info->date, no_date) - 2;
2128 case 'i': /* message-id */
2129 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
2131 case 'n': /* newsgroups */
2132 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
2134 case 'r': /* references */
2135 /* FIXME: using the inreplyto header for reference */
2136 size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
2138 case 'F': /* file */
2139 if (filename == NULL)
2140 filename = folder_item_fetch_msg(info->folder, info->msgnum);
2142 if (filename == NULL) {
2143 g_warning("filename is not set");
2147 size += strlen(filename) - 2;
2156 /* as the string can be quoted, we double the result */
2159 processed_cmd = g_new0(gchar, size);
2163 while (*s != '\0') {
2171 case 's': /* subject */
2172 add_str_default(&p, info->subject,
2175 case 'f': /* from */
2176 add_str_default(&p, info->from,
2180 add_str_default(&p, info->to,
2184 add_str_default(&p, info->cc,
2187 case 'd': /* date */
2188 add_str_default(&p, info->date,
2191 case 'i': /* message-id */
2192 add_str_default(&p, info->msgid,
2195 case 'n': /* newsgroups */
2196 add_str_default(&p, info->newsgroups,
2199 case 'r': /* references */
2200 /* FIXME: using the inreplyto header for references */
2201 add_str_default(&p, info->inreplyto, no_references);
2203 case 'F': /* file */
2204 if (filename != NULL)
2205 add_str_default(&p, filename, NULL);
2224 return processed_cmd;
2226 #undef STRLEN_DEFAULT
2229 /* ************************************************************ */
2233 *\brief Write filtering list to file
2236 *\param prefs_filtering List of filtering conditions
2238 static int prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
2242 for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
2243 gchar *filtering_str = NULL;
2244 gchar *tmp_name = NULL;
2245 FilteringProp *prop = NULL;
2247 if (NULL == (prop = (FilteringProp *) cur->data))
2250 if (NULL == (filtering_str = filteringprop_to_string(prop)))
2253 if (prop->enabled) {
2254 if (fputs("enabled ", fp) == EOF) {
2255 FILE_OP_ERROR("filtering config", "fputs");
2259 if (fputs("disabled ", fp) == EOF) {
2260 FILE_OP_ERROR("filtering config", "fputs");
2265 if (fputs("rulename \"", fp) == EOF) {
2266 FILE_OP_ERROR("filtering config", "fputs");
2267 g_free(filtering_str);
2270 tmp_name = prop->name;
2271 while (tmp_name && *tmp_name != '\0') {
2272 if (*tmp_name != '"') {
2273 if (fputc(*tmp_name, fp) == EOF) {
2274 FILE_OP_ERROR("filtering config", "fputs || fputc");
2275 g_free(filtering_str);
2278 } else if (*tmp_name == '"') {
2279 if (fputc('\\', fp) == EOF ||
2280 fputc('"', fp) == EOF) {
2281 FILE_OP_ERROR("filtering config", "fputs || fputc");
2282 g_free(filtering_str);
2288 if (fputs("\" ", fp) == EOF) {
2289 FILE_OP_ERROR("filtering config", "fputs");
2290 g_free(filtering_str);
2294 if (prop->account_id != 0) {
2297 tmp = g_strdup_printf("account %d ", prop->account_id);
2298 if (fputs(tmp, fp) == EOF) {
2299 FILE_OP_ERROR("filtering config", "fputs");
2306 if(fputs(filtering_str, fp) == EOF ||
2307 fputc('\n', fp) == EOF) {
2308 FILE_OP_ERROR("filtering config", "fputs || fputc");
2309 g_free(filtering_str);
2312 g_free(filtering_str);
2318 typedef struct _NodeLoopData {
2324 *\brief Write matchers from a folder item
2326 *\param node Node with folder info
2327 *\param data File pointer
2329 *\return gboolean FALSE
2331 static gboolean prefs_matcher_write_func(GNode *node, gpointer d)
2334 NodeLoopData *data = (NodeLoopData *)d;
2336 GSList *prefs_filtering;
2339 /* prevent warning */
2340 if (item->path == NULL)
2342 id = folder_item_get_identifier(item);
2345 prefs_filtering = item->prefs->processing;
2347 if (prefs_filtering != NULL) {
2348 if (fprintf(data->fp, "[%s]\n", id) < 0) {
2352 if (prefs_filtering_write(data->fp, prefs_filtering) < 0) {
2356 if (fputc('\n', data->fp) == EOF) {
2368 *\brief Save matchers from folder items
2372 static int prefs_matcher_save(FILE *fp)
2380 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
2383 folder = (Folder *) cur->data;
2384 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2385 prefs_matcher_write_func, &data);
2388 if (data.error == TRUE)
2391 /* pre global rules */
2392 if (fprintf(fp, "[preglobal]\n") < 0 ||
2393 prefs_filtering_write(fp, pre_global_processing) < 0 ||
2394 fputc('\n', fp) == EOF)
2397 /* post global rules */
2398 if (fprintf(fp, "[postglobal]\n") < 0 ||
2399 prefs_filtering_write(fp, post_global_processing) < 0 ||
2400 fputc('\n', fp) == EOF)
2403 /* filtering rules */
2404 if (fprintf(fp, "[filtering]\n") < 0 ||
2405 prefs_filtering_write(fp, filtering_rules) < 0 ||
2406 fputc('\n', fp) == EOF)
2413 *\brief Write filtering / matcher configuration file
2415 void prefs_matcher_write_config(void)
2420 debug_print("Writing matcher configuration...\n");
2422 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2425 if ((pfile = prefs_write_open(rcpath)) == NULL) {
2426 g_warning("failed to write configuration to file\n");
2433 if (prefs_matcher_save(pfile->fp) < 0) {
2434 g_warning("failed to write configuration to file\n");
2435 prefs_file_close_revert(pfile);
2436 } else if (prefs_file_close(pfile) < 0) {
2437 g_warning("failed to save configuration to file\n");
2441 /* ******************************************************************* */
2443 static void matcher_add_rulenames(const gchar *rcpath)
2445 gchar *newpath = g_strconcat(rcpath, ".new", NULL);
2446 FILE *src = g_fopen(rcpath, "rb");
2447 FILE *dst = g_fopen(newpath, "wb");
2448 gchar buf[BUFFSIZE];
2465 while (fgets (buf, sizeof(buf), src) != NULL) {
2466 if (strlen(buf) > 2 && buf[0] != '['
2467 && strncmp(buf, "rulename \"", 10)
2468 && strncmp(buf, "enabled rulename \"", 18)
2469 && strncmp(buf, "disabled rulename \"", 18)) {
2470 r = fwrite("enabled rulename \"\" ",
2471 strlen("enabled rulename \"\" "), 1, dst);
2473 g_message("cannot fwrite rulename\n");
2476 r = fwrite(buf, strlen(buf), 1, dst);
2478 g_message("cannot fwrite rule\n");
2483 move_file(newpath, rcpath, TRUE);
2488 *\brief Read matcher configuration
2490 void prefs_matcher_read_config(void)
2493 gchar *rc_old_format;
2496 create_matchparser_hashtab();
2497 prefs_filtering_clear();
2499 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
2500 rc_old_format = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC,
2501 ".pre_names", NULL);
2503 if (!is_file_exist(rc_old_format) && is_file_exist(rcpath)) {
2504 /* backup file with no rules names, in case
2505 * anything goes wrong */
2506 copy_file(rcpath, rc_old_format, FALSE);
2507 /* now hack the file in order to have it to the new format */
2508 matcher_add_rulenames(rcpath);
2511 g_free(rc_old_format);
2513 f = g_fopen(rcpath, "rb");
2517 matcher_parser_start_parsing(f);
2518 fclose(matcher_parserin);
2521 /* previous version compatibility */
2523 /* g_print("reading filtering\n"); */
2524 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2525 FILTERING_RC, NULL);
2526 f = g_fopen(rcpath, "rb");
2530 matcher_parser_start_parsing(f);
2531 fclose(matcher_parserin);
2534 /* g_print("reading scoring\n"); */
2535 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2537 f = g_fopen(rcpath, "rb");
2541 matcher_parser_start_parsing(f);
2542 fclose(matcher_parserin);