2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2004 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/>.
25 #include <glib/gi18n.h>
37 #include "procheader.h"
39 #include "matcher_parser.h"
40 #include "prefs_gtk.h"
41 #include "addr_compl.h"
43 #include "quoted-printable.h"
46 #include "prefs_common.h"
51 *\brief Keyword lookup element
54 gint id; /*!< keyword id */
55 gchar *str; /*!< keyword */
57 typedef struct _MatchParser MatchParser;
60 *\brief Table with strings and ids used by the lexer and
61 * the parser. New keywords can be added here.
63 static const MatchParser matchparser_tab[] = {
65 {MATCHCRITERIA_ALL, "all"},
66 {MATCHCRITERIA_UNREAD, "unread"},
67 {MATCHCRITERIA_NOT_UNREAD, "~unread"},
68 {MATCHCRITERIA_NEW, "new"},
69 {MATCHCRITERIA_NOT_NEW, "~new"},
70 {MATCHCRITERIA_MARKED, "marked"},
71 {MATCHCRITERIA_NOT_MARKED, "~marked"},
72 {MATCHCRITERIA_DELETED, "deleted"},
73 {MATCHCRITERIA_NOT_DELETED, "~deleted"},
74 {MATCHCRITERIA_REPLIED, "replied"},
75 {MATCHCRITERIA_NOT_REPLIED, "~replied"},
76 {MATCHCRITERIA_FORWARDED, "forwarded"},
77 {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
78 {MATCHCRITERIA_LOCKED, "locked"},
79 {MATCHCRITERIA_NOT_LOCKED, "~locked"},
80 {MATCHCRITERIA_COLORLABEL, "colorlabel"},
81 {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
82 {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
83 {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
84 {MATCHCRITERIA_WATCH_THREAD, "watch_thread"},
85 {MATCHCRITERIA_NOT_WATCH_THREAD, "~watch_thread"},
86 {MATCHCRITERIA_SPAM, "spam"},
87 {MATCHCRITERIA_NOT_SPAM, "~spam"},
90 {MATCHCRITERIA_SUBJECT, "subject"},
91 {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
92 {MATCHCRITERIA_FROM, "from"},
93 {MATCHCRITERIA_NOT_FROM, "~from"},
94 {MATCHCRITERIA_TO, "to"},
95 {MATCHCRITERIA_NOT_TO, "~to"},
96 {MATCHCRITERIA_CC, "cc"},
97 {MATCHCRITERIA_NOT_CC, "~cc"},
98 {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
99 {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
100 {MATCHCRITERIA_TAG, "tag"},
101 {MATCHCRITERIA_NOT_TAG, "~tag"},
102 {MATCHCRITERIA_TAGGED, "tagged"},
103 {MATCHCRITERIA_NOT_TAGGED, "~tagged"},
104 {MATCHCRITERIA_AGE_GREATER, "age_greater"},
105 {MATCHCRITERIA_AGE_LOWER, "age_lower"},
106 {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
107 {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
108 {MATCHCRITERIA_INREPLYTO, "inreplyto"},
109 {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
110 {MATCHCRITERIA_REFERENCES, "references"},
111 {MATCHCRITERIA_NOT_REFERENCES, "~references"},
112 {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
113 {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
114 {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
115 {MATCHCRITERIA_PARTIAL, "partial"},
116 {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
117 {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
118 {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
120 {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
121 {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
122 {MATCHCRITERIA_SIZE_EQUAL, "size_equal"},
124 /* content have to be read */
125 {MATCHCRITERIA_HEADER, "header"},
126 {MATCHCRITERIA_NOT_HEADER, "~header"},
127 {MATCHCRITERIA_HEADERS_PART, "headers_part"},
128 {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
129 {MATCHCRITERIA_MESSAGE, "message"},
130 {MATCHCRITERIA_NOT_MESSAGE, "~message"},
131 {MATCHCRITERIA_BODY_PART, "body_part"},
132 {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
133 {MATCHCRITERIA_TEST, "test"},
134 {MATCHCRITERIA_NOT_TEST, "~test"},
137 {MATCHTYPE_MATCHCASE, "matchcase"},
138 {MATCHTYPE_MATCH, "match"},
139 {MATCHTYPE_REGEXPCASE, "regexpcase"},
140 {MATCHTYPE_REGEXP, "regexp"},
143 {MATCHACTION_SCORE, "score"}, /* for backward compatibility */
144 {MATCHACTION_MOVE, "move"},
145 {MATCHACTION_COPY, "copy"},
146 {MATCHACTION_DELETE, "delete"},
147 {MATCHACTION_MARK, "mark"},
148 {MATCHACTION_UNMARK, "unmark"},
149 {MATCHACTION_LOCK, "lock"},
150 {MATCHACTION_UNLOCK, "unlock"},
151 {MATCHACTION_MARK_AS_READ, "mark_as_read"},
152 {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
153 {MATCHACTION_MARK_AS_SPAM, "mark_as_spam"},
154 {MATCHACTION_MARK_AS_HAM, "mark_as_ham"},
155 {MATCHACTION_FORWARD, "forward"},
156 {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
157 {MATCHACTION_EXECUTE, "execute"},
158 {MATCHACTION_COLOR, "color"},
159 {MATCHACTION_REDIRECT, "redirect"},
160 {MATCHACTION_CHANGE_SCORE, "change_score"},
161 {MATCHACTION_SET_SCORE, "set_score"},
162 {MATCHACTION_STOP, "stop"},
163 {MATCHACTION_HIDE, "hide"},
164 {MATCHACTION_IGNORE, "ignore"},
165 {MATCHACTION_WATCH, "watch"},
166 {MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"},
167 {MATCHACTION_SET_TAG, "set_tag"},
168 {MATCHACTION_UNSET_TAG, "unset_tag"},
169 {MATCHACTION_CLEAR_TAGS, "clear_tags"},
178 extern gboolean debug_filtering_session;
181 *\brief Look up table with keywords defined in \sa matchparser_tab
183 static GHashTable *matchparser_hashtab;
186 *\brief Translate keyword id to keyword string
188 *\param id Id of keyword
190 *\return const gchar * Keyword
192 const gchar *get_matchparser_tab_str(gint id)
196 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
197 if (matchparser_tab[i].id == id)
198 return matchparser_tab[i].str;
204 *\brief Create keyword lookup table
206 static void create_matchparser_hashtab(void)
210 if (matchparser_hashtab) return;
211 matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
212 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
213 g_hash_table_insert(matchparser_hashtab,
214 matchparser_tab[i].str,
215 (gpointer) &matchparser_tab[i]);
219 *\brief Return a keyword id from a keyword string
221 *\param str Keyword string
223 *\return gint Keyword id
225 gint get_matchparser_tab_id(const gchar *str)
229 if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
235 /* **************** data structure allocation **************** */
238 *\brief Allocate a structure for a filtering / scoring
239 * "condition" (a matcher structure)
241 *\param criteria Criteria ID (MATCHCRITERIA_XXXX)
242 *\param header Header string (if criteria is MATCHCRITERIA_HEADER
243 or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
244 *\param matchtype Type of action (MATCHTYPE_XXX)
245 *\param expr String value or expression to check
246 *\param value Integer value to check
248 *\return MatcherProp * Pointer to newly allocated structure
250 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
251 gint matchtype, const gchar *expr,
256 prop = g_new0(MatcherProp, 1);
257 prop->criteria = criteria;
258 prop->header = header != NULL ? g_strdup(header) : NULL;
259 prop->expr = expr != NULL ? g_strdup(expr) : NULL;
260 prop->matchtype = matchtype;
269 *\brief Free a matcher structure
271 *\param prop Pointer to matcher structure allocated with
274 void matcherprop_free(MatcherProp *prop)
277 g_free(prop->header);
278 if (prop->preg != NULL) {
286 *\brief Copy a matcher structure
288 *\param src Matcher structure to copy
290 *\return MatcherProp * Pointer to newly allocated matcher structure
292 MatcherProp *matcherprop_copy(const MatcherProp *src)
294 MatcherProp *prop = g_new0(MatcherProp, 1);
296 prop->criteria = src->criteria;
297 prop->header = src->header ? g_strdup(src->header) : NULL;
298 prop->expr = src->expr ? g_strdup(src->expr) : NULL;
299 prop->matchtype = src->matchtype;
301 prop->preg = NULL; /* will be re-evaluated */
302 prop->value = src->value;
303 prop->error = src->error;
307 /* ************** match ******************************/
309 static gboolean match_with_addresses_in_addressbook
310 (MatcherProp *prop, GSList *address_list, gint type,
311 gchar* folderpath, gint match)
314 gboolean found = FALSE;
317 g_return_val_if_fail(address_list != NULL, FALSE);
319 debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
320 g_slist_length(address_list), folderpath?folderpath:"(null)");
322 if (folderpath == NULL ||
323 strcasecmp(folderpath, _("Any")) == 0 ||
329 start_address_completion(path);
331 for (walk = address_list; walk != NULL; walk = walk->next) {
332 /* exact matching of email address */
333 guint num_addr = complete_address(walk->data);
336 /* skip first item (this is the search string itself) */
338 for (; i < num_addr && !found; i++) {
339 gchar *addr = get_complete_address(i);
340 extract_address(addr);
341 if (strcasecmp(addr, walk->data) == 0) {
345 if (debug_filtering_session
346 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
347 log_print(LOG_DEBUG_FILTERING,
348 "address [ %s ] matches\n",
349 (gchar *)walk->data);
356 if (debug_filtering_session
357 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH
359 log_print(LOG_DEBUG_FILTERING,
360 "address [ %s ] doesn't match\n",
361 (gchar *)walk->data);
365 if (match == MATCH_ALL) {
366 /* if matching all addresses, stop if one doesn't match */
369 if (debug_filtering_session
370 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
371 log_print(LOG_DEBUG_FILTERING,
372 "not all address match (matching all)\n");
376 } else if (match == MATCH_ANY) {
377 /* if matching any address, stop if one does match */
380 if (debug_filtering_session
381 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
382 log_print(LOG_DEBUG_FILTERING,
383 "at least one address matches (matching any)\n");
388 /* MATCH_ONE: there should be only one loop iteration */
391 end_address_completion();
397 *\brief Find out if a string matches a condition
399 *\param prop Matcher structure
400 *\param str String to check
402 *\return gboolean TRUE if str matches the condition in the
405 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str,
406 const gchar *debug_context)
410 gboolean ret = FALSE;
415 switch (prop->matchtype) {
416 case MATCHTYPE_REGEXPCASE:
417 case MATCHTYPE_REGEXP:
418 if (!prop->preg && (prop->error == 0)) {
419 prop->preg = g_new0(regex_t, 1);
420 /* if regexp then don't use the escaped string */
421 if (regcomp(prop->preg, prop->expr,
422 REG_NOSUB | REG_EXTENDED
423 | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
424 ? REG_ICASE : 0)) != 0) {
430 if (prop->preg == NULL)
433 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
439 if (debug_filtering_session
440 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
441 gchar *stripped = g_strdup(str);
443 strretchomp(stripped);
445 log_print(LOG_DEBUG_FILTERING,
446 "%s value [ %s ] matches regular expression [ %s ]\n",
447 debug_context, stripped, prop->expr);
449 log_print(LOG_DEBUG_FILTERING,
450 "%s value [ %s ] doesn't matches regular expression [ %s ]\n",
451 debug_context, stripped, prop->expr);
457 case MATCHTYPE_MATCH:
458 ret = (strstr(str, prop->expr) != NULL);
461 if (debug_filtering_session
462 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
463 gchar *stripped = g_strdup(str);
465 strretchomp(stripped);
467 log_print(LOG_DEBUG_FILTERING,
468 "%s value [ %s ] contains [ %s ] (case sensitive)\n",
469 debug_context, stripped, prop->expr);
471 log_print(LOG_DEBUG_FILTERING,
472 "%s value [ %s ] doesn't contains [ %s ] (case sensitive)\n",
473 debug_context, stripped, prop->expr);
479 /* FIXME: put upper in unesc_str */
480 case MATCHTYPE_MATCHCASE:
481 str2 = alloca(strlen(prop->expr) + 1);
482 strcpy(str2, prop->expr);
484 str1 = alloca(strlen(str) + 1);
487 ret = (strstr(str1, str2) != NULL);
490 if (debug_filtering_session
491 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
492 gchar *stripped = g_strdup(str);
494 strretchomp(stripped);
496 log_print(LOG_DEBUG_FILTERING,
497 "%s value [ %s ] contains [ %s ] (case insensitive)\n",
498 debug_context, stripped, prop->expr);
500 log_print(LOG_DEBUG_FILTERING,
501 "%s [ %s ] doesn't contains [ %s ] (case insensitive)\n",
502 debug_context, stripped, prop->expr);
515 *\brief Find out if a tag matches a condition
517 *\param prop Matcher structure
518 *\param msginfo message to check
520 *\return gboolean TRUE if msginfo matches the condition in the
523 static gboolean matcherprop_tag_match(MatcherProp *prop, MsgInfo *msginfo,
524 const gchar *debug_context)
526 gboolean ret = FALSE;
529 if (msginfo == NULL || msginfo->tags == NULL)
532 for (cur = msginfo->tags; cur; cur = cur->next) {
533 const gchar *str = tags_get_tag(GPOINTER_TO_INT(cur->data));
536 if (matcherprop_string_match(prop, str, debug_context)) {
545 *\brief Find out if the string-ed list matches a condition
547 *\param prop Matcher structure
548 *\param list GSList of strings to check
550 *\return gboolean TRUE if str matches the condition in the
553 static gboolean matcherprop_list_match(MatcherProp *prop, const GSList *list,
554 const gchar *debug_context)
558 for(cur = list; cur != NULL; cur = cur->next) {
559 if (matcherprop_string_match(prop, (gchar *)cur->data, debug_context))
565 /* FIXME body search is a hack. */
566 static gboolean matcherprop_string_decode_match(MatcherProp *prop, const gchar *str,
567 const gchar *debug_context)
571 gboolean res = FALSE;
576 /* we try to decode QP first, because it's faster than base64 */
577 qp_decode_const(tmp, BUFFSIZE-1, str);
578 if (!g_utf8_validate(tmp, -1, NULL)) {
579 utf = conv_codeset_strdup
580 (tmp, conv_get_locale_charset_str_no_utf8(),
582 res = matcherprop_string_match(prop, utf, debug_context);
585 res = matcherprop_string_match(prop, tmp, debug_context);
588 if (res == FALSE && (strchr(prop->expr, '=') || strchr(prop->expr, '_')
589 || strchr(str, '=') || strchr(str, '_'))) {
590 /* if searching for something with an equal char, maybe
591 * we should try to match the non-decoded string.
592 * In case it was not qp-encoded. */
593 if (!g_utf8_validate(str, -1, NULL)) {
594 utf = conv_codeset_strdup
595 (str, conv_get_locale_charset_str_no_utf8(),
597 res = matcherprop_string_match(prop, utf, debug_context);
600 res = matcherprop_string_match(prop, str, debug_context);
604 /* FIXME base64 decoding is too slow, especially since text can
605 * easily be handled as base64. Don't even try now. */
611 typedef struct _thread_data {
618 static void *matcher_test_thread(void *data)
620 thread_data *td = (thread_data *)data;
623 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
624 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
626 result = system(td->cmd);
627 if (result) perror("system");
628 td->done = TRUE; /* let the caller thread join() */
629 return GINT_TO_POINTER(result);
634 *\brief Execute a command defined in the matcher structure
636 *\param prop Pointer to matcher structure
637 *\param info Pointer to message info structure
639 *\return gboolean TRUE if command was executed succesfully
641 static gboolean matcherprop_match_test(const MatcherProp *prop,
649 thread_data *td = g_new0(thread_data, 1);
651 time_t start_time = time(NULL);
654 file = procmsg_get_message_file(info);
659 cmd = matching_build_command(prop->expr, info);
663 #if (defined USE_PTHREAD && ((defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3))) || !defined __GLIBC__))
665 if (debug_filtering_session
666 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
667 log_print(LOG_DEBUG_FILTERING,
668 "starting threaded command [ %s ]\n",
674 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
675 matcher_test_thread, td) != 0)
676 retval = system(cmd);
678 printf("waiting for test thread\n");
680 /* don't let the interface freeze while waiting */
682 if (time(NULL) - start_time > 30) {
688 pthread_join(pt, &res);
689 retval = GPOINTER_TO_INT(res);
690 printf(" test thread returned %d\n", retval);
695 if (debug_filtering_session
696 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
697 log_print(LOG_DEBUG_FILTERING,
698 "starting synchronous command [ %s ]\n",
702 retval = system(cmd);
704 debug_print("Command exit code: %d\n", retval);
707 if (debug_filtering_session
708 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
709 log_print(LOG_DEBUG_FILTERING,
710 "command returned [ %d ]\n",
715 return (retval == 0);
719 *\brief Check if a message matches the condition in a matcher
722 *\param prop Pointer to matcher structure
723 *\param info Pointer to message info
725 *\return gboolean TRUE if a match
727 gboolean matcherprop_match(MatcherProp *prop,
732 switch(prop->criteria) {
733 case MATCHCRITERIA_ALL:
735 case MATCHCRITERIA_UNREAD:
736 return MSG_IS_UNREAD(info->flags);
737 case MATCHCRITERIA_NOT_UNREAD:
738 return !MSG_IS_UNREAD(info->flags);
739 case MATCHCRITERIA_NEW:
740 return MSG_IS_NEW(info->flags);
741 case MATCHCRITERIA_NOT_NEW:
742 return !MSG_IS_NEW(info->flags);
743 case MATCHCRITERIA_MARKED:
744 return MSG_IS_MARKED(info->flags);
745 case MATCHCRITERIA_NOT_MARKED:
746 return !MSG_IS_MARKED(info->flags);
747 case MATCHCRITERIA_DELETED:
748 return MSG_IS_DELETED(info->flags);
749 case MATCHCRITERIA_NOT_DELETED:
750 return !MSG_IS_DELETED(info->flags);
751 case MATCHCRITERIA_REPLIED:
752 return MSG_IS_REPLIED(info->flags);
753 case MATCHCRITERIA_NOT_REPLIED:
754 return !MSG_IS_REPLIED(info->flags);
755 case MATCHCRITERIA_FORWARDED:
756 return MSG_IS_FORWARDED(info->flags);
757 case MATCHCRITERIA_NOT_FORWARDED:
758 return !MSG_IS_FORWARDED(info->flags);
759 case MATCHCRITERIA_LOCKED:
760 return MSG_IS_LOCKED(info->flags);
761 case MATCHCRITERIA_NOT_LOCKED:
762 return !MSG_IS_LOCKED(info->flags);
763 case MATCHCRITERIA_SPAM:
764 return MSG_IS_SPAM(info->flags);
765 case MATCHCRITERIA_NOT_SPAM:
766 return !MSG_IS_SPAM(info->flags);
767 case MATCHCRITERIA_COLORLABEL:
769 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
770 gboolean ret = (color == prop->value);
773 if (debug_filtering_session
774 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
776 log_print(LOG_DEBUG_FILTERING,
777 "message color value [ %d ] matches color value [ %d ]\n",
780 log_print(LOG_DEBUG_FILTERING,
781 "message color value [ %d ] doesn't matches color value [ %d ]\n",
787 case MATCHCRITERIA_NOT_COLORLABEL:
789 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
790 gboolean ret = (color != prop->value);
793 if (debug_filtering_session
794 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
796 log_print(LOG_DEBUG_FILTERING,
797 "message color value [ %d ] matches color value [ %d ]\n",
800 log_print(LOG_DEBUG_FILTERING,
801 "message color value [ %d ] doesn't matches color value [ %d ]\n",
807 case MATCHCRITERIA_IGNORE_THREAD:
808 return MSG_IS_IGNORE_THREAD(info->flags);
809 case MATCHCRITERIA_NOT_IGNORE_THREAD:
810 return !MSG_IS_IGNORE_THREAD(info->flags);
811 case MATCHCRITERIA_WATCH_THREAD:
812 return MSG_IS_WATCH_THREAD(info->flags);
813 case MATCHCRITERIA_NOT_WATCH_THREAD:
814 return !MSG_IS_WATCH_THREAD(info->flags);
815 case MATCHCRITERIA_SUBJECT:
816 return matcherprop_string_match(prop, info->subject,
817 prefs_common_translated_header_name("Subject:"));
818 case MATCHCRITERIA_NOT_SUBJECT:
819 return !matcherprop_string_match(prop, info->subject,
820 prefs_common_translated_header_name("Subject:"));
821 case MATCHCRITERIA_FROM:
822 case MATCHCRITERIA_NOT_FROM:
827 context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
828 ret = matcherprop_string_match(prop, info->from, context);
830 return (prop->criteria == MATCHCRITERIA_FROM)? ret : !ret;
832 case MATCHCRITERIA_TO:
833 case MATCHCRITERIA_NOT_TO:
838 context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
839 ret = matcherprop_string_match(prop, info->to, context);
841 return (prop->criteria == MATCHCRITERIA_TO)? ret : !ret;
843 case MATCHCRITERIA_CC:
844 case MATCHCRITERIA_NOT_CC:
849 context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
850 ret = matcherprop_string_match(prop, info->cc, context);
852 return (prop->criteria == MATCHCRITERIA_CC)? ret : !ret;
854 case MATCHCRITERIA_TO_OR_CC:
856 gchar *context1, *context2;
859 context1 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
860 context2 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
861 ret = matcherprop_string_match(prop, info->to, context1)
862 || matcherprop_string_match(prop, info->cc, context2);
867 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
869 gchar *context1, *context2;
872 context1 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
873 context2 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
874 ret = !(matcherprop_string_match(prop, info->to, context1)
875 || matcherprop_string_match(prop, info->cc, context2));
880 case MATCHCRITERIA_TAG:
881 case MATCHCRITERIA_NOT_TAG:
885 ret = matcherprop_tag_match(prop, info, _("Tag"));
886 return (prop->criteria == MATCHCRITERIA_TAG)? ret : !ret;
888 case MATCHCRITERIA_TAGGED:
889 case MATCHCRITERIA_NOT_TAGGED:
893 ret = (info->tags != NULL);
894 return (prop->criteria == MATCHCRITERIA_TAGGED)? ret : !ret;
896 case MATCHCRITERIA_AGE_GREATER:
902 age = ((t - info->date_t) / (60 * 60 * 24));
903 ret = (age > prop->value);
906 if (debug_filtering_session
907 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
909 log_print(LOG_DEBUG_FILTERING,
910 "message age [ %d ] is greater than [ %d ]\n",
913 log_print(LOG_DEBUG_FILTERING,
914 "message age [ %d ] is not greater than [ %d ]\n",
920 case MATCHCRITERIA_AGE_LOWER:
926 age = ((t - info->date_t) / (60 * 60 * 24));
927 ret = (age < prop->value);
930 if (debug_filtering_session
931 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
933 log_print(LOG_DEBUG_FILTERING,
934 "message age [ %d ] is lower than [ %d ]\n",
937 log_print(LOG_DEBUG_FILTERING,
938 "message age [ %d ] is not lower than [ %d ]\n",
944 case MATCHCRITERIA_SCORE_GREATER:
946 gboolean ret = (info->score > prop->value);
949 if (debug_filtering_session
950 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
952 log_print(LOG_DEBUG_FILTERING,
953 "message score [ %d ] is greater than [ %d ]\n",
954 info->score, prop->value);
956 log_print(LOG_DEBUG_FILTERING,
957 "message score [ %d ] is not greater than [ %d ]\n",
958 info->score, prop->value);
963 case MATCHCRITERIA_SCORE_LOWER:
965 gboolean ret = (info->score < prop->value);
968 if (debug_filtering_session
969 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
971 log_print(LOG_DEBUG_FILTERING,
972 "message score [ %d ] is lower than [ %d ]\n",
973 info->score, prop->value);
975 log_print(LOG_DEBUG_FILTERING,
976 "message score [ %d ] is not lower than [ %d ]\n",
977 info->score, prop->value);
982 case MATCHCRITERIA_SCORE_EQUAL:
984 gboolean ret = (info->score == prop->value);
987 if (debug_filtering_session
988 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
990 log_print(LOG_DEBUG_FILTERING,
991 "message score [ %d ] is equal to [ %d ]\n",
992 info->score, prop->value);
994 log_print(LOG_DEBUG_FILTERING,
995 "message score [ %d ] is not equal to [ %d ]\n",
996 info->score, prop->value);
1001 case MATCHCRITERIA_SIZE_GREATER:
1003 /* FIXME: info->size is an off_t */
1004 gboolean ret = (info->size > (off_t) prop->value);
1007 if (debug_filtering_session
1008 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1010 log_print(LOG_DEBUG_FILTERING,
1011 "message size [ %ld ] is greater than [ %d ]\n",
1012 info->size, prop->value);
1014 log_print(LOG_DEBUG_FILTERING,
1015 "message size [ %ld ] is not greater than [ %d ]\n",
1016 info->size, prop->value);
1021 case MATCHCRITERIA_SIZE_SMALLER:
1023 /* FIXME: info->size is an off_t */
1024 gboolean ret = (info->size < (off_t) prop->value);
1027 if (debug_filtering_session
1028 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1030 log_print(LOG_DEBUG_FILTERING,
1031 "message size [ %ld ] is smaller than [ %d ]\n",
1032 info->size, prop->value);
1034 log_print(LOG_DEBUG_FILTERING,
1035 "message size [ %ld ] is not smaller than [ %d ]\n",
1036 info->size, prop->value);
1041 case MATCHCRITERIA_SIZE_EQUAL:
1043 /* FIXME: info->size is an off_t */
1044 gboolean ret = (info->size == (off_t) prop->value);
1047 if (debug_filtering_session
1048 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1050 log_print(LOG_DEBUG_FILTERING,
1051 "message size [ %ld ] is equal to [ %d ]\n",
1052 info->size, prop->value);
1054 log_print(LOG_DEBUG_FILTERING,
1055 "message size [ %ld ] is not equal to [ %d ]\n",
1056 info->size, prop->value);
1061 case MATCHCRITERIA_PARTIAL:
1063 /* FIXME: info->size is an off_t */
1064 gboolean ret = (info->total_size != 0 && info->size != (off_t)info->total_size);
1067 if (debug_filtering_session
1068 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1070 log_print(LOG_DEBUG_FILTERING,
1071 "message is partially downloaded, size [ %ld ] is less than total size [ %d ])\n",
1072 info->size, info->total_size);
1074 log_print(LOG_DEBUG_FILTERING,
1075 "message is not partially downloaded\n");
1080 case MATCHCRITERIA_NOT_PARTIAL:
1082 /* FIXME: info->size is an off_t */
1083 gboolean ret = (info->total_size == 0 || info->size == (off_t)info->total_size);
1086 if (debug_filtering_session
1087 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1089 log_print(LOG_DEBUG_FILTERING,
1090 "message is not partially downloaded\n");
1092 log_print(LOG_DEBUG_FILTERING,
1093 "message is partially downloaded, size [ %ld ] is less than total size [ %d ])\n",
1094 info->size, info->total_size);
1099 case MATCHCRITERIA_NEWSGROUPS:
1100 case MATCHCRITERIA_NOT_NEWSGROUPS:
1105 context = g_strdup_printf(_("%s header"),
1106 prefs_common_translated_header_name("Newsgroups:"));
1107 ret = matcherprop_string_match(prop, info->newsgroups, context);
1109 return (prop->criteria == MATCHCRITERIA_NEWSGROUPS)? ret : !ret;
1111 case MATCHCRITERIA_INREPLYTO:
1112 case MATCHCRITERIA_NOT_INREPLYTO:
1117 context = g_strdup_printf(_("%s header"),
1118 prefs_common_translated_header_name("In-Reply-To:"));
1119 ret = matcherprop_string_match(prop, info->inreplyto, context);
1121 return (prop->criteria == MATCHCRITERIA_INREPLYTO)? ret : !ret;
1123 case MATCHCRITERIA_REFERENCES:
1124 case MATCHCRITERIA_NOT_REFERENCES:
1129 context = g_strdup_printf(_("%s header"),
1130 prefs_common_translated_header_name("References:"));
1131 ret = matcherprop_list_match(prop, info->references, context);
1133 return (prop->criteria == MATCHCRITERIA_REFERENCES)? ret : !ret;
1135 case MATCHCRITERIA_TEST:
1136 return matcherprop_match_test(prop, info);
1137 case MATCHCRITERIA_NOT_TEST:
1138 return !matcherprop_match_test(prop, info);
1144 /* ********************* MatcherList *************************** */
1147 *\brief Create a new list of matchers
1149 *\param matchers List of matcher structures
1150 *\param bool_and Operator
1152 *\return MatcherList * New list
1154 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
1158 cond = g_new0(MatcherList, 1);
1160 cond->matchers = matchers;
1161 cond->bool_and = bool_and;
1167 *\brief Frees a list of matchers
1169 *\param cond List of matchers
1171 void matcherlist_free(MatcherList *cond)
1175 g_return_if_fail(cond);
1176 for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
1177 matcherprop_free((MatcherProp *) l->data);
1183 *\brief Skip all headers in a message file
1185 *\param fp Message file
1187 static void matcherlist_skip_headers(FILE *fp)
1189 gchar buf[BUFFSIZE];
1191 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
1196 *\brief Check if a header matches a matcher condition
1198 *\param matcher Matcher structure to check header for
1199 *\param buf Header name
1201 *\return boolean TRUE if matching header
1203 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
1206 gboolean result = FALSE;
1207 Header *header = NULL;
1209 switch (matcher->criteria) {
1210 case MATCHCRITERIA_HEADER:
1211 case MATCHCRITERIA_NOT_HEADER:
1212 header = procheader_parse_header(buf);
1215 if (procheader_headername_equal(header->name,
1217 if (matcher->criteria == MATCHCRITERIA_HEADER)
1218 result = matcherprop_string_match(matcher, header->body, _("header"));
1220 result = !matcherprop_string_match(matcher, header->body, _("header"));
1221 procheader_header_free(header);
1225 procheader_header_free(header);
1228 case MATCHCRITERIA_HEADERS_PART:
1229 return matcherprop_string_match(matcher, buf, _("header line"));
1230 case MATCHCRITERIA_NOT_HEADERS_PART:
1231 return !matcherprop_string_match(matcher, buf, _("headers line"));
1232 case MATCHCRITERIA_MESSAGE:
1233 return matcherprop_string_decode_match(matcher, buf, _("message line"));
1234 case MATCHCRITERIA_NOT_MESSAGE:
1235 return !matcherprop_string_decode_match(matcher, buf, _("message line"));
1236 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1237 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1239 GSList *address_list = NULL;
1240 gint match = MATCH_ONE;
1241 gboolean found = FALSE;
1243 /* how many address headers are me trying to mach? */
1244 if (strcasecmp(matcher->header, _("Any")) == 0)
1246 else if (strcasecmp(matcher->header, Q_("Filtering Matcher Menu|All")) == 0)
1249 if (match == MATCH_ONE) {
1250 /* matching one address header exactly, is that the right one? */
1251 header = procheader_parse_header(buf);
1253 !procheader_headername_equal(header->name, matcher->header))
1255 address_list = address_list_append(address_list, header->body);
1256 if (address_list == NULL)
1260 header = procheader_parse_header(buf);
1263 /* address header is one of the headers we have to match when checking
1264 for any address header or all address headers? */
1265 if (procheader_headername_equal(header->name, "From") ||
1266 procheader_headername_equal(header->name, "To") ||
1267 procheader_headername_equal(header->name, "Cc") ||
1268 procheader_headername_equal(header->name, "Reply-To") ||
1269 procheader_headername_equal(header->name, "Sender"))
1270 address_list = address_list_append(address_list, header->body);
1271 if (address_list == NULL)
1275 found = match_with_addresses_in_addressbook
1276 (matcher, address_list, matcher->criteria,
1277 matcher->expr, match);
1278 g_slist_free(address_list);
1280 if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
1291 *\brief Check if the matcher structure wants headers to
1294 *\param matcher Matcher structure
1296 *\return gboolean TRUE if the matcher structure describes
1297 * a header match condition
1299 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
1301 switch (matcher->criteria) {
1302 case MATCHCRITERIA_HEADER:
1303 case MATCHCRITERIA_NOT_HEADER:
1304 case MATCHCRITERIA_HEADERS_PART:
1305 case MATCHCRITERIA_NOT_HEADERS_PART:
1306 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1307 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1315 *\brief Check if the matcher structure wants the message
1316 * to be matched (just perform an action on any
1319 *\param matcher Matcher structure
1321 *\return gboolean TRUE if matcher condition should match
1324 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
1326 switch (matcher->criteria) {
1327 case MATCHCRITERIA_MESSAGE:
1328 case MATCHCRITERIA_NOT_MESSAGE:
1336 *\brief Check if a list of conditions matches one header in
1339 *\param matchers List of conditions
1340 *\param fp Message file
1342 *\return gboolean TRUE if one of the headers is matched by
1343 * the list of conditions.
1345 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
1348 gchar buf[BUFFSIZE];
1350 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
1351 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1352 MatcherProp *matcher = (MatcherProp *) l->data;
1353 gint match = MATCH_ANY;
1358 /* determine the match range (all, any are our concern here) */
1359 if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
1360 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1363 } else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
1364 matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
1365 Header *header = NULL;
1367 /* address header is one of the headers we have to match when checking
1368 for any address header or all address headers? */
1369 header = procheader_parse_header(buf);
1371 (procheader_headername_equal(header->name, "From") ||
1372 procheader_headername_equal(header->name, "To") ||
1373 procheader_headername_equal(header->name, "Cc") ||
1374 procheader_headername_equal(header->name, "Reply-To") ||
1375 procheader_headername_equal(header->name, "Sender"))) {
1377 if (strcasecmp(matcher->header, _("Any")) == 0)
1379 else if (strcasecmp(matcher->header, Q_("Filtering Matcher Menu|All")) == 0)
1384 /* further call to matcherprop_match_one_header() can't match
1385 and it irrelevant, so: don't alter the match result */
1390 /* ZERO line must NOT match for the rule to match.
1392 if (match == MATCH_ALL) {
1393 if (matcherprop_match_one_header(matcher, buf)) {
1394 matcher->result = TRUE;
1396 matcher->result = FALSE;
1397 matcher->done = TRUE;
1399 /* else, just one line matching is enough for the rule to match
1401 } else if (matcherprop_criteria_headers(matcher) ||
1402 matcherprop_criteria_message(matcher)) {
1403 if (matcherprop_match_one_header(matcher, buf)) {
1404 matcher->result = TRUE;
1405 matcher->done = TRUE;
1409 /* if the rule matched and the matchers are OR, no need to
1410 * check the others */
1411 if (matcher->result && matcher->done) {
1412 if (!matchers->bool_and)
1422 *\brief Check if a matcher wants to check the message body
1424 *\param matcher Matcher structure
1426 *\return gboolean TRUE if body must be matched.
1428 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
1430 switch (matcher->criteria) {
1431 case MATCHCRITERIA_BODY_PART:
1432 case MATCHCRITERIA_NOT_BODY_PART:
1440 *\brief Check if a (line) string matches the criteria
1441 * described by a matcher structure
1443 *\param matcher Matcher structure
1446 *\return gboolean TRUE if string matches criteria
1448 static gboolean matcherprop_match_line(MatcherProp *matcher, const gchar *line)
1450 switch (matcher->criteria) {
1451 case MATCHCRITERIA_BODY_PART:
1452 case MATCHCRITERIA_MESSAGE:
1453 return matcherprop_string_decode_match(matcher, line, _("body line"));
1454 case MATCHCRITERIA_NOT_BODY_PART:
1455 case MATCHCRITERIA_NOT_MESSAGE:
1456 return !matcherprop_string_decode_match(matcher, line, _("body line"));
1462 *\brief Check if a line in a message file's body matches
1465 *\param matchers List of conditions
1466 *\param fp Message file
1468 *\return gboolean TRUE if succesful match
1470 static gboolean matcherlist_match_body(MatcherList *matchers, FILE *fp)
1473 gchar buf[BUFFSIZE];
1475 while (fgets(buf, sizeof(buf), fp) != NULL) {
1476 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1477 MatcherProp *matcher = (MatcherProp *) l->data;
1482 /* if the criteria is ~body_part or ~message, ZERO lines
1483 * must NOT match for the rule to match. */
1484 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1485 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1486 if (matcherprop_match_line(matcher, buf)) {
1487 matcher->result = TRUE;
1489 matcher->result = FALSE;
1490 matcher->done = TRUE;
1492 /* else, just one line has to match */
1493 } else if (matcherprop_criteria_body(matcher) ||
1494 matcherprop_criteria_message(matcher)) {
1495 if (matcherprop_match_line(matcher, buf)) {
1496 matcher->result = TRUE;
1497 matcher->done = TRUE;
1501 /* if the matchers are OR'ed and the rule matched,
1502 * no need to check the others. */
1503 if (matcher->result && matcher->done) {
1504 if (!matchers->bool_and)
1513 *\brief Check if a message file matches criteria
1515 *\param matchers Criteria
1516 *\param info Message info
1517 *\param result Default result
1519 *\return gboolean TRUE if matched
1521 static gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
1524 gboolean read_headers;
1530 /* file need to be read ? */
1532 read_headers = FALSE;
1534 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1535 MatcherProp *matcher = (MatcherProp *) l->data;
1537 if (matcherprop_criteria_headers(matcher))
1538 read_headers = TRUE;
1539 if (matcherprop_criteria_body(matcher))
1541 if (matcherprop_criteria_message(matcher)) {
1542 read_headers = TRUE;
1545 matcher->result = FALSE;
1546 matcher->done = FALSE;
1549 if (!read_headers && !read_body)
1552 file = procmsg_get_message_file_full(info, read_headers, read_body);
1556 if ((fp = g_fopen(file, "rb")) == NULL) {
1557 FILE_OP_ERROR(file, "fopen");
1562 /* read the headers */
1565 if (matcherlist_match_headers(matchers, fp))
1568 matcherlist_skip_headers(fp);
1573 matcherlist_match_body(matchers, fp);
1576 for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
1577 MatcherProp *matcher = (MatcherProp *) l->data;
1579 if (matcherprop_criteria_headers(matcher) ||
1580 matcherprop_criteria_body(matcher) ||
1581 matcherprop_criteria_message(matcher)) {
1582 if (matcher->result) {
1583 if (!matchers->bool_and) {
1589 if (matchers->bool_and) {
1605 *\brief Test list of conditions on a message.
1607 *\param matchers List of conditions
1608 *\param info Message info
1610 *\return gboolean TRUE if matched
1612 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
1620 if (matchers->bool_and)
1625 /* test the cached elements */
1627 for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
1628 MatcherProp *matcher = (MatcherProp *) l->data;
1630 if (debug_filtering_session) {
1631 gchar *buf = matcherprop_to_string(matcher);
1632 log_print(LOG_DEBUG_FILTERING, _("checking if message matches [ %s ]\n"), buf);
1636 switch(matcher->criteria) {
1637 case MATCHCRITERIA_ALL:
1638 case MATCHCRITERIA_UNREAD:
1639 case MATCHCRITERIA_NOT_UNREAD:
1640 case MATCHCRITERIA_NEW:
1641 case MATCHCRITERIA_NOT_NEW:
1642 case MATCHCRITERIA_MARKED:
1643 case MATCHCRITERIA_NOT_MARKED:
1644 case MATCHCRITERIA_DELETED:
1645 case MATCHCRITERIA_NOT_DELETED:
1646 case MATCHCRITERIA_REPLIED:
1647 case MATCHCRITERIA_NOT_REPLIED:
1648 case MATCHCRITERIA_FORWARDED:
1649 case MATCHCRITERIA_NOT_FORWARDED:
1650 case MATCHCRITERIA_LOCKED:
1651 case MATCHCRITERIA_NOT_LOCKED:
1652 case MATCHCRITERIA_SPAM:
1653 case MATCHCRITERIA_NOT_SPAM:
1654 case MATCHCRITERIA_COLORLABEL:
1655 case MATCHCRITERIA_NOT_COLORLABEL:
1656 case MATCHCRITERIA_IGNORE_THREAD:
1657 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1658 case MATCHCRITERIA_WATCH_THREAD:
1659 case MATCHCRITERIA_NOT_WATCH_THREAD:
1660 case MATCHCRITERIA_SUBJECT:
1661 case MATCHCRITERIA_NOT_SUBJECT:
1662 case MATCHCRITERIA_FROM:
1663 case MATCHCRITERIA_NOT_FROM:
1664 case MATCHCRITERIA_TO:
1665 case MATCHCRITERIA_NOT_TO:
1666 case MATCHCRITERIA_CC:
1667 case MATCHCRITERIA_NOT_CC:
1668 case MATCHCRITERIA_TO_OR_CC:
1669 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1670 case MATCHCRITERIA_TAG:
1671 case MATCHCRITERIA_NOT_TAG:
1672 case MATCHCRITERIA_TAGGED:
1673 case MATCHCRITERIA_NOT_TAGGED:
1674 case MATCHCRITERIA_AGE_GREATER:
1675 case MATCHCRITERIA_AGE_LOWER:
1676 case MATCHCRITERIA_NEWSGROUPS:
1677 case MATCHCRITERIA_NOT_NEWSGROUPS:
1678 case MATCHCRITERIA_INREPLYTO:
1679 case MATCHCRITERIA_NOT_INREPLYTO:
1680 case MATCHCRITERIA_REFERENCES:
1681 case MATCHCRITERIA_NOT_REFERENCES:
1682 case MATCHCRITERIA_SCORE_GREATER:
1683 case MATCHCRITERIA_SCORE_LOWER:
1684 case MATCHCRITERIA_SCORE_EQUAL:
1685 case MATCHCRITERIA_SIZE_GREATER:
1686 case MATCHCRITERIA_SIZE_SMALLER:
1687 case MATCHCRITERIA_SIZE_EQUAL:
1688 case MATCHCRITERIA_TEST:
1689 case MATCHCRITERIA_NOT_TEST:
1690 case MATCHCRITERIA_PARTIAL:
1691 case MATCHCRITERIA_NOT_PARTIAL:
1692 if (matcherprop_match(matcher, info)) {
1693 if (!matchers->bool_and) {
1694 if (debug_filtering_session)
1695 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1700 if (matchers->bool_and) {
1701 if (debug_filtering_session)
1702 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1709 /* test the condition on the file */
1711 if (matcherlist_match_file(matchers, info, result)) {
1712 if (!matchers->bool_and) {
1713 if (debug_filtering_session)
1714 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1718 if (matchers->bool_and) {
1719 if (debug_filtering_session)
1720 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1725 if (debug_filtering_session) {
1727 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1729 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1735 static gint quote_filter_str(gchar * result, guint size,
1745 for(p = path ; * p != '\0' ; p ++) {
1747 if ((* p != '\"') && (* p != '\\')) {
1748 if (remaining > 0) {
1754 result[size - 1] = '\0';
1759 if (remaining >= 2) {
1767 result[size - 1] = '\0';
1772 if (remaining > 0) {
1776 result[size - 1] = '\0';
1784 gchar * matcher_quote_str(const gchar * src)
1789 len = strlen(src) * 2 + 1;
1790 res = g_malloc(len);
1791 quote_filter_str(res, len, src);
1797 *\brief Convert a matcher structure to a string
1799 *\param matcher Matcher structure
1801 *\return gchar * Newly allocated string
1803 gchar *matcherprop_to_string(MatcherProp *matcher)
1805 gchar *matcher_str = NULL;
1806 const gchar *criteria_str;
1807 const gchar *matchtype_str;
1809 gchar * quoted_expr;
1810 gchar * quoted_header;
1812 criteria_str = NULL;
1813 for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
1814 if (matchparser_tab[i].id == matcher->criteria)
1815 criteria_str = matchparser_tab[i].str;
1817 if (criteria_str == NULL)
1820 switch (matcher->criteria) {
1821 case MATCHCRITERIA_AGE_GREATER:
1822 case MATCHCRITERIA_AGE_LOWER:
1823 case MATCHCRITERIA_SCORE_GREATER:
1824 case MATCHCRITERIA_SCORE_LOWER:
1825 case MATCHCRITERIA_SCORE_EQUAL:
1826 case MATCHCRITERIA_SIZE_GREATER:
1827 case MATCHCRITERIA_SIZE_SMALLER:
1828 case MATCHCRITERIA_SIZE_EQUAL:
1829 case MATCHCRITERIA_COLORLABEL:
1830 case MATCHCRITERIA_NOT_COLORLABEL:
1831 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1832 case MATCHCRITERIA_ALL:
1833 case MATCHCRITERIA_UNREAD:
1834 case MATCHCRITERIA_NOT_UNREAD:
1835 case MATCHCRITERIA_NEW:
1836 case MATCHCRITERIA_NOT_NEW:
1837 case MATCHCRITERIA_MARKED:
1838 case MATCHCRITERIA_NOT_MARKED:
1839 case MATCHCRITERIA_DELETED:
1840 case MATCHCRITERIA_NOT_DELETED:
1841 case MATCHCRITERIA_REPLIED:
1842 case MATCHCRITERIA_NOT_REPLIED:
1843 case MATCHCRITERIA_FORWARDED:
1844 case MATCHCRITERIA_NOT_FORWARDED:
1845 case MATCHCRITERIA_LOCKED:
1846 case MATCHCRITERIA_NOT_LOCKED:
1847 case MATCHCRITERIA_SPAM:
1848 case MATCHCRITERIA_NOT_SPAM:
1849 case MATCHCRITERIA_PARTIAL:
1850 case MATCHCRITERIA_NOT_PARTIAL:
1851 case MATCHCRITERIA_IGNORE_THREAD:
1852 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1853 case MATCHCRITERIA_WATCH_THREAD:
1854 case MATCHCRITERIA_NOT_WATCH_THREAD:
1855 case MATCHCRITERIA_TAGGED:
1856 case MATCHCRITERIA_NOT_TAGGED:
1857 return g_strdup(criteria_str);
1858 case MATCHCRITERIA_TEST:
1859 case MATCHCRITERIA_NOT_TEST:
1860 quoted_expr = matcher_quote_str(matcher->expr);
1861 matcher_str = g_strdup_printf("%s \"%s\"",
1862 criteria_str, quoted_expr);
1863 g_free(quoted_expr);
1865 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1866 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1867 quoted_header = matcher_quote_str(matcher->header);
1868 quoted_expr = matcher_quote_str(matcher->expr);
1869 matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
1870 criteria_str, quoted_header, quoted_expr);
1871 g_free(quoted_header);
1872 g_free(quoted_expr);
1876 matchtype_str = NULL;
1877 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
1878 if (matchparser_tab[i].id == matcher->matchtype)
1879 matchtype_str = matchparser_tab[i].str;
1882 if (matchtype_str == NULL)
1885 switch (matcher->matchtype) {
1886 case MATCHTYPE_MATCH:
1887 case MATCHTYPE_MATCHCASE:
1888 case MATCHTYPE_REGEXP:
1889 case MATCHTYPE_REGEXPCASE:
1890 quoted_expr = matcher_quote_str(matcher->expr);
1891 if (matcher->header) {
1892 quoted_header = matcher_quote_str(matcher->header);
1893 matcher_str = g_strdup_printf
1894 ("%s \"%s\" %s \"%s\"",
1895 criteria_str, quoted_header,
1896 matchtype_str, quoted_expr);
1897 g_free(quoted_header);
1900 matcher_str = g_strdup_printf
1901 ("%s %s \"%s\"", criteria_str,
1902 matchtype_str, quoted_expr);
1903 g_free(quoted_expr);
1911 *\brief Convert a list of conditions to a string
1913 *\param matchers List of conditions
1915 *\return gchar * Newly allocated string
1917 gchar *matcherlist_to_string(const MatcherList *matchers)
1923 gchar *result = NULL;
1925 count = g_slist_length(matchers->matchers);
1926 vstr = g_new(gchar *, count + 1);
1928 for (l = matchers->matchers, cur_str = vstr; l != NULL;
1929 l = g_slist_next(l), cur_str ++) {
1930 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1931 if (*cur_str == NULL)
1936 if (matchers->bool_and)
1937 result = g_strjoinv(" & ", vstr);
1939 result = g_strjoinv(" | ", vstr);
1941 for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1949 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
1950 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
1952 static void add_str_default(gchar ** dest,
1953 const gchar * s, const gchar * d)
1955 gchar quoted_str[4096];
1963 quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
1964 strcpy(* dest, quoted_str);
1966 (* dest) += strlen(* dest);
1969 /* matching_build_command() - preferably cmd should be unescaped */
1971 *\brief Build the command line to execute
1973 *\param cmd String with command line specifiers
1974 *\param info Message info to use for command
1976 *\return gchar * Newly allocated string
1978 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
1980 const gchar *s = cmd;
1981 gchar *filename = NULL;
1982 gchar *processed_cmd;
1986 const gchar *const no_subject = _("(none)") ;
1987 const gchar *const no_from = _("(none)") ;
1988 const gchar *const no_to = _("(none)") ;
1989 const gchar *const no_cc = _("(none)") ;
1990 const gchar *const no_date = _("(none)") ;
1991 const gchar *const no_msgid = _("(none)") ;
1992 const gchar *const no_newsgroups = _("(none)") ;
1993 const gchar *const no_references = _("(none)") ;
1995 size = STRLEN_ZERO(cmd) + 1;
1996 while (*s != '\0') {
2003 case 's': /* subject */
2004 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
2006 case 'f': /* from */
2007 size += STRLEN_DEFAULT(info->from, no_from) - 2;
2010 size += STRLEN_DEFAULT(info->to, no_to) - 2;
2013 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
2015 case 'd': /* date */
2016 size += STRLEN_DEFAULT(info->date, no_date) - 2;
2018 case 'i': /* message-id */
2019 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
2021 case 'n': /* newsgroups */
2022 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
2024 case 'r': /* references */
2025 /* FIXME: using the inreplyto header for reference */
2026 size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
2028 case 'F': /* file */
2029 if (filename == NULL)
2030 filename = folder_item_fetch_msg(info->folder, info->msgnum);
2032 if (filename == NULL) {
2033 g_warning("filename is not set");
2037 size += strlen(filename) - 2;
2046 /* as the string can be quoted, we double the result */
2049 processed_cmd = g_new0(gchar, size);
2053 while (*s != '\0') {
2061 case 's': /* subject */
2062 add_str_default(&p, info->subject,
2065 case 'f': /* from */
2066 add_str_default(&p, info->from,
2070 add_str_default(&p, info->to,
2074 add_str_default(&p, info->cc,
2077 case 'd': /* date */
2078 add_str_default(&p, info->date,
2081 case 'i': /* message-id */
2082 add_str_default(&p, info->msgid,
2085 case 'n': /* newsgroups */
2086 add_str_default(&p, info->newsgroups,
2089 case 'r': /* references */
2090 /* FIXME: using the inreplyto header for references */
2091 add_str_default(&p, info->inreplyto, no_references);
2093 case 'F': /* file */
2094 if (filename != NULL)
2095 add_str_default(&p, filename, NULL);
2114 return processed_cmd;
2116 #undef STRLEN_DEFAULT
2119 /* ************************************************************ */
2123 *\brief Write filtering list to file
2126 *\param prefs_filtering List of filtering conditions
2128 static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
2132 for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
2133 gchar *filtering_str = NULL;
2134 gchar *tmp_name = NULL;
2135 FilteringProp *prop = NULL;
2137 if (NULL == (prop = (FilteringProp *) cur->data))
2140 if (NULL == (filtering_str = filteringprop_to_string(prop)))
2143 if (prop->enabled) {
2144 if (fputs("enabled ", fp) == EOF) {
2145 FILE_OP_ERROR("filtering config", "fputs");
2149 if (fputs("disabled ", fp) == EOF) {
2150 FILE_OP_ERROR("filtering config", "fputs");
2155 if (fputs("rulename \"", fp) == EOF) {
2156 FILE_OP_ERROR("filtering config", "fputs");
2157 g_free(filtering_str);
2160 tmp_name = prop->name;
2161 while (tmp_name && *tmp_name != '\0') {
2162 if (*tmp_name != '"') {
2163 if (fputc(*tmp_name, fp) == EOF) {
2164 FILE_OP_ERROR("filtering config", "fputs || fputc");
2165 g_free(filtering_str);
2168 } else if (*tmp_name == '"') {
2169 if (fputc('\\', fp) == EOF ||
2170 fputc('"', fp) == EOF) {
2171 FILE_OP_ERROR("filtering config", "fputs || fputc");
2172 g_free(filtering_str);
2178 if (fputs("\" ", fp) == EOF) {
2179 FILE_OP_ERROR("filtering config", "fputs");
2180 g_free(filtering_str);
2184 if (prop->account_id != 0) {
2187 tmp = g_strdup_printf("account %d ", prop->account_id);
2188 if (fputs(tmp, fp) == EOF) {
2189 FILE_OP_ERROR("filtering config", "fputs");
2196 if(fputs(filtering_str, fp) == EOF ||
2197 fputc('\n', fp) == EOF) {
2198 FILE_OP_ERROR("filtering config", "fputs || fputc");
2199 g_free(filtering_str);
2202 g_free(filtering_str);
2207 *\brief Write matchers from a folder item
2209 *\param node Node with folder info
2210 *\param data File pointer
2212 *\return gboolean FALSE
2214 static gboolean prefs_matcher_write_func(GNode *node, gpointer data)
2219 GSList *prefs_filtering;
2222 /* prevent warning */
2223 if (item->path == NULL)
2225 id = folder_item_get_identifier(item);
2228 prefs_filtering = item->prefs->processing;
2230 if (prefs_filtering != NULL) {
2231 fprintf(fp, "[%s]\n", id);
2232 prefs_filtering_write(fp, prefs_filtering);
2242 *\brief Save matchers from folder items
2246 static void prefs_matcher_save(FILE *fp)
2250 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
2253 folder = (Folder *) cur->data;
2254 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2255 prefs_matcher_write_func, fp);
2258 /* pre global rules */
2259 fprintf(fp, "[preglobal]\n");
2260 prefs_filtering_write(fp, pre_global_processing);
2263 /* post global rules */
2264 fprintf(fp, "[postglobal]\n");
2265 prefs_filtering_write(fp, post_global_processing);
2268 /* filtering rules */
2269 fprintf(fp, "[filtering]\n");
2270 prefs_filtering_write(fp, filtering_rules);
2275 *\brief Write filtering / matcher configuration file
2277 void prefs_matcher_write_config(void)
2282 debug_print("Writing matcher configuration...\n");
2284 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2287 if ((pfile = prefs_write_open(rcpath)) == NULL) {
2288 g_warning("failed to write configuration to file\n");
2294 prefs_matcher_save(pfile->fp);
2298 if (prefs_file_close(pfile) < 0) {
2299 g_warning("failed to write configuration to file\n");
2304 /* ******************************************************************* */
2306 static void matcher_add_rulenames(const gchar *rcpath)
2308 gchar *newpath = g_strconcat(rcpath, ".new", NULL);
2309 FILE *src = g_fopen(rcpath, "rb");
2310 FILE *dst = g_fopen(newpath, "wb");
2311 gchar buf[BUFFSIZE];
2319 while (fgets (buf, sizeof(buf), src) != NULL) {
2320 if (strlen(buf) > 2 && buf[0] != '['
2321 && strncmp(buf, "rulename \"", 10)
2322 && strncmp(buf, "enabled rulename \"", 18)
2323 && strncmp(buf, "disabled rulename \"", 18)) {
2324 fwrite("enabled rulename \"\" ",
2325 strlen("enabled rulename \"\" "), 1, dst);
2327 fwrite(buf, strlen(buf), 1, dst);
2331 move_file(newpath, rcpath, TRUE);
2336 *\brief Read matcher configuration
2338 void prefs_matcher_read_config(void)
2341 gchar *rc_old_format;
2344 create_matchparser_hashtab();
2345 prefs_filtering_clear();
2347 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
2348 rc_old_format = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC,
2349 ".pre_names", NULL);
2351 if (!is_file_exist(rc_old_format) && is_file_exist(rcpath)) {
2352 /* backup file with no rules names, in case
2353 * anything goes wrong */
2354 copy_file(rcpath, rc_old_format, FALSE);
2355 /* now hack the file in order to have it to the new format */
2356 matcher_add_rulenames(rcpath);
2359 g_free(rc_old_format);
2361 f = g_fopen(rcpath, "rb");
2365 matcher_parser_start_parsing(f);
2366 fclose(matcher_parserin);
2369 /* previous version compatibility */
2371 /* printf("reading filtering\n"); */
2372 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2373 FILTERING_RC, NULL);
2374 f = g_fopen(rcpath, "rb");
2378 matcher_parser_start_parsing(f);
2379 fclose(matcher_parserin);
2382 /* printf("reading scoring\n"); */
2383 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2385 f = g_fopen(rcpath, "rb");
2389 matcher_parser_start_parsing(f);
2390 fclose(matcher_parserin);