2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2014 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_AGE_GREATER_HOURS, "age_greater_hours"},
114 {MATCHCRITERIA_AGE_LOWER_HOURS, "age_lower_hours"},
115 {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
116 {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
117 {MATCHCRITERIA_INREPLYTO, "inreplyto"},
118 {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
119 {MATCHCRITERIA_REFERENCES, "references"},
120 {MATCHCRITERIA_NOT_REFERENCES, "~references"},
121 {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
122 {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
123 {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
124 {MATCHCRITERIA_PARTIAL, "partial"},
125 {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
126 {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
127 {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
129 {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
130 {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
131 {MATCHCRITERIA_SIZE_EQUAL, "size_equal"},
133 /* content have to be read */
134 {MATCHCRITERIA_HEADER, "header"},
135 {MATCHCRITERIA_NOT_HEADER, "~header"},
136 {MATCHCRITERIA_HEADERS_PART, "headers_part"},
137 {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
138 {MATCHCRITERIA_HEADERS_CONT, "headers_cont"},
139 {MATCHCRITERIA_NOT_HEADERS_CONT, "~headers_cont"},
140 {MATCHCRITERIA_MESSAGE, "message"},
141 {MATCHCRITERIA_NOT_MESSAGE, "~message"},
142 {MATCHCRITERIA_BODY_PART, "body_part"},
143 {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
144 {MATCHCRITERIA_TEST, "test"},
145 {MATCHCRITERIA_NOT_TEST, "~test"},
148 {MATCHTYPE_MATCHCASE, "matchcase"},
149 {MATCHTYPE_MATCH, "match"},
150 {MATCHTYPE_REGEXPCASE, "regexpcase"},
151 {MATCHTYPE_REGEXP, "regexp"},
154 {MATCHACTION_SCORE, "score"}, /* for backward compatibility */
155 {MATCHACTION_MOVE, "move"},
156 {MATCHACTION_COPY, "copy"},
157 {MATCHACTION_DELETE, "delete"},
158 {MATCHACTION_MARK, "mark"},
159 {MATCHACTION_UNMARK, "unmark"},
160 {MATCHACTION_LOCK, "lock"},
161 {MATCHACTION_UNLOCK, "unlock"},
162 {MATCHACTION_MARK_AS_READ, "mark_as_read"},
163 {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
164 {MATCHACTION_MARK_AS_SPAM, "mark_as_spam"},
165 {MATCHACTION_MARK_AS_HAM, "mark_as_ham"},
166 {MATCHACTION_FORWARD, "forward"},
167 {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
168 {MATCHACTION_EXECUTE, "execute"},
169 {MATCHACTION_COLOR, "color"},
170 {MATCHACTION_REDIRECT, "redirect"},
171 {MATCHACTION_CHANGE_SCORE, "change_score"},
172 {MATCHACTION_SET_SCORE, "set_score"},
173 {MATCHACTION_STOP, "stop"},
174 {MATCHACTION_HIDE, "hide"},
175 {MATCHACTION_IGNORE, "ignore"},
176 {MATCHACTION_WATCH, "watch"},
177 {MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"},
178 {MATCHACTION_SET_TAG, "set_tag"},
179 {MATCHACTION_UNSET_TAG, "unset_tag"},
180 {MATCHACTION_CLEAR_TAGS, "clear_tags"},
204 static gchar *context_str[N_CONTEXT_STRS];
206 void matcher_init(void)
208 if (context_str[CONTEXT_SUBJECT] != NULL)
211 context_str[CONTEXT_SUBJECT] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Subject:"));
212 context_str[CONTEXT_FROM] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
213 context_str[CONTEXT_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
214 context_str[CONTEXT_CC] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
215 context_str[CONTEXT_NEWSGROUPS] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Newsgroups:"));
216 context_str[CONTEXT_IN_REPLY_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("In-Reply-To:"));
217 context_str[CONTEXT_REFERENCES] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("References:"));
218 context_str[CONTEXT_HEADER] = g_strdup(_("header"));
219 context_str[CONTEXT_HEADER_LINE] = g_strdup(_("header line"));
220 context_str[CONTEXT_BODY_LINE] = g_strdup(_("body line"));
221 context_str[CONTEXT_TAG] = g_strdup(_("tag"));
224 void matcher_done(void)
227 for (i = 0; i < N_CONTEXT_STRS; i++) {
228 g_free(context_str[i]);
229 context_str[i] = NULL;
233 extern gboolean debug_filtering_session;
236 *\brief Look up table with keywords defined in \sa matchparser_tab
238 static GHashTable *matchparser_hashtab;
241 *\brief Translate keyword id to keyword string
243 *\param id Id of keyword
245 *\return const gchar * Keyword
247 const gchar *get_matchparser_tab_str(gint id)
251 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
252 if (matchparser_tab[i].id == id)
253 return matchparser_tab[i].str;
259 *\brief Create keyword lookup table
261 static void create_matchparser_hashtab(void)
265 if (matchparser_hashtab) return;
266 matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
267 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
268 g_hash_table_insert(matchparser_hashtab,
269 matchparser_tab[i].str,
270 (gpointer) &matchparser_tab[i]);
274 *\brief Return a keyword id from a keyword string
276 *\param str Keyword string
278 *\return gint Keyword id
280 gint get_matchparser_tab_id(const gchar *str)
284 if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
290 /* **************** data structure allocation **************** */
293 *\brief Allocate a structure for a filtering / scoring
294 * "condition" (a matcher structure)
296 *\param criteria Criteria ID (MATCHCRITERIA_XXXX)
297 *\param header Header string (if criteria is MATCHCRITERIA_HEADER
298 or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
299 *\param matchtype Type of action (MATCHTYPE_XXX)
300 *\param expr String value or expression to check
301 *\param value Integer value to check
303 *\return MatcherProp * Pointer to newly allocated structure
305 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
306 gint matchtype, const gchar *expr,
311 prop = g_new0(MatcherProp, 1);
312 prop->criteria = criteria;
313 prop->header = header != NULL ? g_strdup(header) : NULL;
315 prop->expr = expr != NULL ? g_strdup(expr) : NULL;
317 prop->matchtype = matchtype;
328 *\brief Free a matcher structure
330 *\param prop Pointer to matcher structure allocated with
333 void matcherprop_free(MatcherProp *prop)
336 g_free(prop->header);
338 if (prop->preg != NULL) {
347 *\brief Copy a matcher structure
349 *\param src Matcher structure to copy
351 *\return MatcherProp * Pointer to newly allocated matcher structure
353 MatcherProp *matcherprop_copy(const MatcherProp *src)
355 MatcherProp *prop = g_new0(MatcherProp, 1);
357 prop->criteria = src->criteria;
358 prop->header = src->header ? g_strdup(src->header) : NULL;
359 prop->expr = src->expr ? g_strdup(src->expr) : NULL;
360 prop->matchtype = src->matchtype;
363 prop->preg = NULL; /* will be re-evaluated */
365 prop->value = src->value;
366 prop->error = src->error;
370 /* ************** match ******************************/
372 static gboolean match_with_addresses_in_addressbook
373 (MatcherProp *prop, GSList *address_list, gint type,
374 gchar* folderpath, gint match)
377 gboolean found = FALSE;
380 cm_return_val_if_fail(address_list != NULL, FALSE);
382 debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
383 g_slist_length(address_list), folderpath?folderpath:"(null)");
385 if (folderpath == NULL ||
386 strcasecmp(folderpath, "Any") == 0 ||
392 start_address_completion(path);
394 for (walk = address_list; walk != NULL; walk = walk->next) {
395 /* exact matching of email address */
396 guint num_addr = complete_address(walk->data);
399 /* skip first item (this is the search string itself) */
401 for (; i < num_addr && !found; i++) {
402 gchar *addr = get_complete_address(i);
403 extract_address(addr);
404 if (strcasecmp(addr, walk->data) == 0) {
408 if (debug_filtering_session
409 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
410 log_print(LOG_DEBUG_FILTERING,
411 "address [ %s ] matches\n",
412 (gchar *)walk->data);
419 if (debug_filtering_session
420 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH
422 log_print(LOG_DEBUG_FILTERING,
423 "address [ %s ] does NOT match\n",
424 (gchar *)walk->data);
428 if (match == MATCH_ALL) {
429 /* if matching all addresses, stop if one doesn't match */
432 if (debug_filtering_session
433 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
434 log_print(LOG_DEBUG_FILTERING,
435 "not all address match (matching all)\n");
439 } else if (match == MATCH_ANY) {
440 /* if matching any address, stop if one does match */
443 if (debug_filtering_session
444 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
445 log_print(LOG_DEBUG_FILTERING,
446 "at least one address matches (matching any)\n");
451 /* MATCH_ONE: there should be only one loop iteration */
454 end_address_completion();
460 *\brief Find out if a string matches a condition
462 *\param prop Matcher structure
463 *\param str String to check
465 *\return gboolean TRUE if str matches the condition in the
468 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str,
469 const gchar *debug_context)
473 gboolean ret = FALSE;
474 gboolean should_free = FALSE;
478 if (prop->matchtype == MATCHTYPE_REGEXPCASE ||
479 prop->matchtype == MATCHTYPE_MATCHCASE) {
480 str1 = g_utf8_casefold(str, -1);
481 down_expr = g_utf8_casefold(prop->expr, -1);
485 down_expr = (gchar *)prop->expr;
489 switch (prop->matchtype) {
490 case MATCHTYPE_REGEXPCASE:
491 case MATCHTYPE_REGEXP:
493 if (!prop->preg && (prop->error == 0)) {
494 prop->preg = g_new0(regex_t, 1);
495 /* if regexp then don't use the escaped string */
496 if (regcomp(prop->preg, down_expr,
497 REG_NOSUB | REG_EXTENDED
498 | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
499 ? REG_ICASE : 0)) != 0) {
505 if (prop->preg == NULL) {
510 if (regexec(prop->preg, str1, 0, NULL, 0) == 0)
516 if (debug_filtering_session
517 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
518 gchar *stripped = g_strdup(str);
520 strretchomp(stripped);
522 log_print(LOG_DEBUG_FILTERING,
523 "%s value [ %s ] matches regular expression [ %s ] (%s)\n",
524 debug_context, stripped, prop->expr,
525 prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
527 log_print(LOG_DEBUG_FILTERING,
528 "%s value [ %s ] does NOT match regular expression [ %s ] (%s)\n",
529 debug_context, stripped, prop->expr,
530 prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
536 case MATCHTYPE_MATCHCASE:
537 case MATCHTYPE_MATCH:
538 ret = (strstr(str1, down_expr) != NULL);
541 if (debug_filtering_session
542 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
543 gchar *stripped = g_strdup(str);
545 strretchomp(stripped);
547 log_print(LOG_DEBUG_FILTERING,
548 "%s value [ %s ] contains [ %s ] (%s)\n",
549 debug_context, stripped, prop->expr,
550 prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
552 log_print(LOG_DEBUG_FILTERING,
553 "%s value [ %s ] does NOT contain [ %s ] (%s)\n",
554 debug_context, stripped, prop->expr,
555 prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
574 *\brief Find out if a tag matches a condition
576 *\param prop Matcher structure
577 *\param msginfo message to check
579 *\return gboolean TRUE if msginfo matches the condition in the
582 static gboolean matcherprop_tag_match(MatcherProp *prop, MsgInfo *msginfo,
583 const gchar *debug_context)
585 gboolean ret = FALSE;
588 if (msginfo == NULL || msginfo->tags == NULL)
591 for (cur = msginfo->tags; cur; cur = cur->next) {
592 const gchar *str = tags_get_tag(GPOINTER_TO_INT(cur->data));
595 if (matcherprop_string_match(prop, str, debug_context)) {
604 *\brief Find out if the string-ed list matches a condition
606 *\param prop Matcher structure
607 *\param list GSList of strings to check
609 *\return gboolean TRUE if str matches the condition in the
612 static gboolean matcherprop_list_match(MatcherProp *prop, const GSList *list,
613 const gchar *debug_context)
617 for(cur = list; cur != NULL; cur = cur->next) {
618 if (matcherprop_string_match(prop, (gchar *)cur->data, debug_context))
624 static gboolean matcherprop_header_line_match(MatcherProp *prop, const gchar *hdr,
625 const gchar *str, const gboolean both,
626 const gchar *debug_context)
628 gboolean res = FALSE;
630 if (hdr == NULL || str == NULL)
634 /* Search in all header names and content.
636 gchar *line = g_strdup_printf("%s %s", hdr, str);
637 res = matcherprop_string_match(prop, line, debug_context);
640 /* Search only in content and exclude private headers.
641 * E.g.: searching for "H foo" in folder x/foo would return
642 * *all* mail as SCF and RMID will match.
643 * Searching for "H sent" would return all resent messages
644 * as "Resent-From: whatever" will match.
646 if (procheader_header_is_internal(hdr))
648 res = matcherprop_string_match(prop, str, debug_context);
655 typedef struct _thread_data {
662 static void *matcher_test_thread(void *data)
664 thread_data *td = (thread_data *)data;
667 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
668 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
670 result = system(td->cmd);
671 td->done = TRUE; /* let the caller thread join() */
672 return GINT_TO_POINTER(result);
677 *\brief Execute a command defined in the matcher structure
679 *\param prop Pointer to matcher structure
680 *\param info Pointer to message info structure
682 *\return gboolean TRUE if command was executed succesfully
684 static gboolean matcherprop_match_test(const MatcherProp *prop,
693 thread_data *td = g_new0(thread_data, 1);
695 time_t start_time = time(NULL);
698 file = procmsg_get_message_file(info);
707 cmd = matching_build_command(prop->expr, info);
717 if (debug_filtering_session
718 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
719 log_print(LOG_DEBUG_FILTERING,
720 "starting threaded command [ %s ]\n",
726 if (pthread_attr_init(&pta) != 0 ||
727 pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_JOINABLE) != 0 ||
728 pthread_create(&pt, &pta, matcher_test_thread, td) != 0)
729 retval = system(cmd);
731 debug_print("waiting for test thread\n");
733 /* don't let the interface freeze while waiting */
735 if (time(NULL) - start_time > 30) {
741 pthread_join(pt, &res);
742 retval = GPOINTER_TO_INT(res);
743 debug_print(" test thread returned %d\n", retval);
748 if (debug_filtering_session
749 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
750 log_print(LOG_DEBUG_FILTERING,
751 "starting synchronous command [ %s ]\n",
755 retval = system(cmd);
757 debug_print("Command exit code: %d\n", retval);
760 if (debug_filtering_session
761 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
762 log_print(LOG_DEBUG_FILTERING,
763 "command returned [ %d ]\n",
768 return (retval == 0);
772 *\brief Check if a message matches the condition in a matcher
775 *\param prop Pointer to matcher structure
776 *\param info Pointer to message info
778 *\return gboolean TRUE if a match
780 static gboolean matcherprop_match(MatcherProp *prop,
784 gint age_mult_hours = 1;
786 switch(prop->criteria) {
787 case MATCHCRITERIA_ALL:
789 case MATCHCRITERIA_UNREAD:
790 return MSG_IS_UNREAD(info->flags);
791 case MATCHCRITERIA_NOT_UNREAD:
792 return !MSG_IS_UNREAD(info->flags);
793 case MATCHCRITERIA_NEW:
794 return MSG_IS_NEW(info->flags);
795 case MATCHCRITERIA_NOT_NEW:
796 return !MSG_IS_NEW(info->flags);
797 case MATCHCRITERIA_MARKED:
798 return MSG_IS_MARKED(info->flags);
799 case MATCHCRITERIA_NOT_MARKED:
800 return !MSG_IS_MARKED(info->flags);
801 case MATCHCRITERIA_DELETED:
802 return MSG_IS_DELETED(info->flags);
803 case MATCHCRITERIA_NOT_DELETED:
804 return !MSG_IS_DELETED(info->flags);
805 case MATCHCRITERIA_REPLIED:
806 return MSG_IS_REPLIED(info->flags);
807 case MATCHCRITERIA_NOT_REPLIED:
808 return !MSG_IS_REPLIED(info->flags);
809 case MATCHCRITERIA_FORWARDED:
810 return MSG_IS_FORWARDED(info->flags);
811 case MATCHCRITERIA_NOT_FORWARDED:
812 return !MSG_IS_FORWARDED(info->flags);
813 case MATCHCRITERIA_LOCKED:
814 return MSG_IS_LOCKED(info->flags);
815 case MATCHCRITERIA_NOT_LOCKED:
816 return !MSG_IS_LOCKED(info->flags);
817 case MATCHCRITERIA_SPAM:
818 return MSG_IS_SPAM(info->flags);
819 case MATCHCRITERIA_NOT_SPAM:
820 return !MSG_IS_SPAM(info->flags);
821 case MATCHCRITERIA_HAS_ATTACHMENT:
822 return MSG_IS_WITH_ATTACHMENT(info->flags);
823 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
824 return !MSG_IS_WITH_ATTACHMENT(info->flags);
825 case MATCHCRITERIA_SIGNED:
826 return MSG_IS_SIGNED(info->flags);
827 case MATCHCRITERIA_NOT_SIGNED:
828 return !MSG_IS_SIGNED(info->flags);
829 case MATCHCRITERIA_COLORLABEL:
831 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
832 gboolean ret = (color == prop->value);
835 if (debug_filtering_session
836 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
838 log_print(LOG_DEBUG_FILTERING,
839 "message color value [ %d ] matches color value [ %d ]\n",
842 log_print(LOG_DEBUG_FILTERING,
843 "message color value [ %d ] does NOT match color value [ %d ]\n",
849 case MATCHCRITERIA_NOT_COLORLABEL:
851 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
852 gboolean ret = (color != prop->value);
855 if (debug_filtering_session
856 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
858 log_print(LOG_DEBUG_FILTERING,
859 "message color value [ %d ] matches color value [ %d ]\n",
862 log_print(LOG_DEBUG_FILTERING,
863 "message color value [ %d ] does NOT match color value [ %d ]\n",
869 case MATCHCRITERIA_IGNORE_THREAD:
870 return MSG_IS_IGNORE_THREAD(info->flags);
871 case MATCHCRITERIA_NOT_IGNORE_THREAD:
872 return !MSG_IS_IGNORE_THREAD(info->flags);
873 case MATCHCRITERIA_WATCH_THREAD:
874 return MSG_IS_WATCH_THREAD(info->flags);
875 case MATCHCRITERIA_NOT_WATCH_THREAD:
876 return !MSG_IS_WATCH_THREAD(info->flags);
877 case MATCHCRITERIA_SUBJECT:
878 return matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
879 case MATCHCRITERIA_NOT_SUBJECT:
880 return !matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
881 case MATCHCRITERIA_FROM:
882 return matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
883 case MATCHCRITERIA_NOT_FROM:
884 return !matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
885 case MATCHCRITERIA_TO:
886 return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
887 case MATCHCRITERIA_NOT_TO:
888 return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
889 case MATCHCRITERIA_CC:
890 return matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
891 case MATCHCRITERIA_NOT_CC:
892 return !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
893 case MATCHCRITERIA_TO_OR_CC:
894 return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
895 || matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
896 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
897 return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
898 && !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
899 case MATCHCRITERIA_TAG:
900 return matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
901 case MATCHCRITERIA_NOT_TAG:
902 return !matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
903 case MATCHCRITERIA_TAGGED:
904 return info->tags != NULL;
905 case MATCHCRITERIA_NOT_TAGGED:
906 return info->tags == NULL;
907 case MATCHCRITERIA_AGE_GREATER:
909 /* Fallthrough intended */
910 case MATCHCRITERIA_AGE_GREATER_HOURS:
916 age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
917 ret = (age >= prop->value);
920 if (debug_filtering_session
921 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
923 log_print(LOG_DEBUG_FILTERING,
924 "message age [ %d ] is greater than [ %d ]\n",
927 log_print(LOG_DEBUG_FILTERING,
928 "message age [ %d ] is not greater than [ %d ]\n",
934 case MATCHCRITERIA_AGE_LOWER:
936 /* Fallthrough intended */
937 case MATCHCRITERIA_AGE_LOWER_HOURS:
943 age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
944 ret = (age < prop->value);
947 if (debug_filtering_session
948 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
950 log_print(LOG_DEBUG_FILTERING,
951 "message age [ %d ] is lower than [ %d ]\n",
954 log_print(LOG_DEBUG_FILTERING,
955 "message age [ %d ] is not lower than [ %d ]\n",
961 case MATCHCRITERIA_SCORE_GREATER:
963 gboolean ret = (info->score > prop->value);
966 if (debug_filtering_session
967 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
969 log_print(LOG_DEBUG_FILTERING,
970 "message score [ %d ] is greater than [ %d ]\n",
971 info->score, prop->value);
973 log_print(LOG_DEBUG_FILTERING,
974 "message score [ %d ] is not greater than [ %d ]\n",
975 info->score, prop->value);
980 case MATCHCRITERIA_SCORE_LOWER:
982 gboolean ret = (info->score < prop->value);
985 if (debug_filtering_session
986 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
988 log_print(LOG_DEBUG_FILTERING,
989 "message score [ %d ] is lower than [ %d ]\n",
990 info->score, prop->value);
992 log_print(LOG_DEBUG_FILTERING,
993 "message score [ %d ] is not lower than [ %d ]\n",
994 info->score, prop->value);
999 case MATCHCRITERIA_SCORE_EQUAL:
1001 gboolean ret = (info->score == prop->value);
1004 if (debug_filtering_session
1005 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1007 log_print(LOG_DEBUG_FILTERING,
1008 "message score [ %d ] is equal to [ %d ]\n",
1009 info->score, prop->value);
1011 log_print(LOG_DEBUG_FILTERING,
1012 "message score [ %d ] is not equal to [ %d ]\n",
1013 info->score, prop->value);
1018 case MATCHCRITERIA_SIZE_GREATER:
1020 /* FIXME: info->size is a goffset */
1021 gboolean ret = (info->size > (goffset) prop->value);
1024 if (debug_filtering_session
1025 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1027 log_print(LOG_DEBUG_FILTERING,
1028 "message size is greater than [ %d ]\n",
1031 log_print(LOG_DEBUG_FILTERING,
1032 "message size is not greater than [ %d ]\n",
1038 case MATCHCRITERIA_SIZE_SMALLER:
1040 /* FIXME: info->size is a goffset */
1041 gboolean ret = (info->size < (goffset) prop->value);
1044 if (debug_filtering_session
1045 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1047 log_print(LOG_DEBUG_FILTERING,
1048 "message size is smaller than [ %d ]\n",
1051 log_print(LOG_DEBUG_FILTERING,
1052 "message size is not smaller than [ %d ]\n",
1058 case MATCHCRITERIA_SIZE_EQUAL:
1060 /* FIXME: info->size is a goffset */
1061 gboolean ret = (info->size == (goffset) prop->value);
1064 if (debug_filtering_session
1065 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1067 log_print(LOG_DEBUG_FILTERING,
1068 "message size is equal to [ %d ]\n",
1071 log_print(LOG_DEBUG_FILTERING,
1072 "message size is not equal to [ %d ]\n",
1078 case MATCHCRITERIA_PARTIAL:
1080 /* FIXME: info->size is a goffset */
1081 gboolean ret = (info->total_size != 0 && info->size != (goffset)info->total_size);
1084 if (debug_filtering_session
1085 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1087 log_print(LOG_DEBUG_FILTERING,
1088 "message is partially downloaded, size is less than total size [ %d ])\n",
1091 log_print(LOG_DEBUG_FILTERING,
1092 "message is not partially downloaded\n");
1097 case MATCHCRITERIA_NOT_PARTIAL:
1099 /* FIXME: info->size is a goffset */
1100 gboolean ret = (info->total_size == 0 || info->size == (goffset)info->total_size);
1103 if (debug_filtering_session
1104 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1106 log_print(LOG_DEBUG_FILTERING,
1107 "message is not partially downloaded\n");
1109 log_print(LOG_DEBUG_FILTERING,
1110 "message is partially downloaded, size is less than total size [ %d ])\n",
1116 case MATCHCRITERIA_NEWSGROUPS:
1117 return matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
1118 case MATCHCRITERIA_NOT_NEWSGROUPS:
1119 return !matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
1120 case MATCHCRITERIA_INREPLYTO:
1121 return matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
1122 case MATCHCRITERIA_NOT_INREPLYTO:
1123 return !matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
1124 case MATCHCRITERIA_REFERENCES:
1125 return matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
1126 case MATCHCRITERIA_NOT_REFERENCES:
1127 return !matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
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;
1161 *\brief Builds a single regular expresion from an array of srings.
1163 *\param strings The lines containing the different sub-regexp.
1165 *\return The newly allocated regexp string.
1167 static gchar *build_complete_regexp(gchar **strings)
1171 while (strings && strings[i] && *strings[i]) {
1172 int old_len = expr ? strlen(expr):0;
1174 gchar *tmpstr = NULL;
1176 if (g_utf8_validate(strings[i], -1, NULL))
1177 tmpstr = g_strdup(strings[i]);
1179 tmpstr = conv_codeset_strdup(strings[i],
1180 conv_get_locale_charset_str_no_utf8(),
1183 if (strstr(tmpstr, "\n"))
1184 *(strstr(tmpstr, "\n")) = '\0';
1186 new_len = strlen(tmpstr);
1188 expr = g_realloc(expr,
1189 expr ? (old_len + strlen("|()") + new_len + 1)
1190 : (strlen("()") + new_len + 1));
1193 strcpy(expr + old_len, "|(");
1194 strcpy(expr + old_len + 2, tmpstr);
1195 strcpy(expr + old_len + 2 + new_len, ")");
1197 strcpy(expr+old_len, "(");
1198 strcpy(expr+old_len + 1, tmpstr);
1199 strcpy(expr+old_len + 1 + new_len, ")");
1209 *\brief Create a new list of matchers from a multi-line string
1211 *\param lines String with "\n"-separated expressions
1212 *\param bool_and Operator
1213 *\param case_sensitive If the matching is case sensitive or not
1215 *\return MatcherList * New matcher list
1217 MatcherList *matcherlist_new_from_lines(gchar *lines, gboolean bool_and,
1218 gboolean case_sensitive)
1220 MatcherProp *m = NULL;
1221 GSList *matchers = NULL;
1222 gchar **strings = g_strsplit(lines, "\n", -1);
1226 expr = build_complete_regexp(strings);
1227 debug_print("building matcherprop for expr '%s'\n", expr?expr:"NULL");
1229 m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
1230 case_sensitive? MATCHTYPE_REGEXP: MATCHTYPE_REGEXPCASE,
1233 /* print error message */
1234 debug_print("failed to allocate memory for matcherprop\n");
1236 matchers = g_slist_append(matchers, m);
1242 while (strings && strings[i] && *strings[i]) {
1243 m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
1244 case_sensitive? MATCHTYPE_MATCH: MATCHTYPE_MATCHCASE,
1247 /* print error message */
1248 debug_print("failed to allocate memory for matcherprop\n");
1250 matchers = g_slist_append(matchers, m);
1255 g_strfreev(strings);
1257 return matcherlist_new(matchers, bool_and);
1261 *\brief Frees a list of matchers
1263 *\param cond List of matchers
1265 void matcherlist_free(MatcherList *cond)
1269 cm_return_if_fail(cond);
1270 for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
1271 matcherprop_free((MatcherProp *) l->data);
1273 g_slist_free(cond->matchers);
1278 *\brief Skip all headers in a message file
1280 *\param fp Message file
1282 static void matcherlist_skip_headers(FILE *fp)
1284 gchar buf[BUFFSIZE];
1286 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
1291 *\brief Check if a header matches a matcher condition
1293 *\param matcher Matcher structure to check header for
1294 *\param buf Header name
1296 *\return boolean TRUE if matching header
1298 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
1301 gboolean result = FALSE;
1302 Header *header = NULL;
1304 switch (matcher->criteria) {
1305 case MATCHCRITERIA_HEADER:
1306 case MATCHCRITERIA_NOT_HEADER:
1307 header = procheader_parse_header(buf);
1310 if (procheader_headername_equal(header->name,
1312 if (matcher->criteria == MATCHCRITERIA_HEADER)
1313 result = matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
1315 result = !matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
1316 procheader_header_free(header);
1320 procheader_header_free(header);
1323 case MATCHCRITERIA_HEADERS_PART:
1324 case MATCHCRITERIA_HEADERS_CONT:
1325 case MATCHCRITERIA_MESSAGE:
1326 header = procheader_parse_header(buf);
1329 result = matcherprop_header_line_match(matcher,
1330 header->name, header->body,
1331 (matcher->criteria == MATCHCRITERIA_HEADERS_PART),
1332 context_str[CONTEXT_HEADER_LINE]);
1333 procheader_header_free(header);
1335 case MATCHCRITERIA_NOT_HEADERS_CONT:
1336 case MATCHCRITERIA_NOT_HEADERS_PART:
1337 case MATCHCRITERIA_NOT_MESSAGE:
1338 header = procheader_parse_header(buf);
1341 result = !matcherprop_header_line_match(matcher,
1342 header->name, header->body,
1343 (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART),
1344 context_str[CONTEXT_HEADER_LINE]);
1345 procheader_header_free(header);
1347 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1348 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1350 GSList *address_list = NULL;
1351 gint match = MATCH_ONE;
1352 gboolean found = FALSE;
1354 /* how many address headers are we trying to match? */
1355 if (strcasecmp(matcher->header, "Any") == 0)
1357 else if (strcasecmp(matcher->header, "All") == 0)
1360 if (match == MATCH_ONE) {
1361 /* matching one address header exactly, is that the right one? */
1362 header = procheader_parse_header(buf);
1364 !procheader_headername_equal(header->name, matcher->header))
1366 address_list = address_list_append(address_list, header->body);
1367 if (address_list == NULL)
1371 header = procheader_parse_header(buf);
1374 /* address header is one of the headers we have to match when checking
1375 for any address header or all address headers? */
1376 if (procheader_headername_equal(header->name, "From") ||
1377 procheader_headername_equal(header->name, "To") ||
1378 procheader_headername_equal(header->name, "Cc") ||
1379 procheader_headername_equal(header->name, "Reply-To") ||
1380 procheader_headername_equal(header->name, "Sender"))
1381 address_list = address_list_append(address_list, header->body);
1382 if (address_list == NULL)
1386 found = match_with_addresses_in_addressbook
1387 (matcher, address_list, matcher->criteria,
1388 matcher->expr, match);
1389 g_slist_free(address_list);
1391 if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
1402 *\brief Check if the matcher structure wants headers to
1405 *\param matcher Matcher structure
1407 *\return gboolean TRUE if the matcher structure describes
1408 * a header match condition
1410 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
1412 switch (matcher->criteria) {
1413 case MATCHCRITERIA_HEADER:
1414 case MATCHCRITERIA_NOT_HEADER:
1415 case MATCHCRITERIA_HEADERS_PART:
1416 case MATCHCRITERIA_HEADERS_CONT:
1417 case MATCHCRITERIA_NOT_HEADERS_PART:
1418 case MATCHCRITERIA_NOT_HEADERS_CONT:
1419 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1420 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1428 *\brief Check if the matcher structure wants the message
1429 * to be matched (just perform an action on any
1432 *\param matcher Matcher structure
1434 *\return gboolean TRUE if matcher condition should match
1437 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
1439 switch (matcher->criteria) {
1440 case MATCHCRITERIA_MESSAGE:
1441 case MATCHCRITERIA_NOT_MESSAGE:
1449 *\brief Check if a list of conditions matches one header in
1452 *\param matchers List of conditions
1453 *\param fp Message file
1455 *\return gboolean TRUE if one of the headers is matched by
1456 * the list of conditions.
1458 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
1461 gchar buf[BUFFSIZE];
1463 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
1464 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1465 MatcherProp *matcher = (MatcherProp *) l->data;
1466 gint match = MATCH_ANY;
1471 /* determine the match range (all, any are our concern here) */
1472 if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
1473 matcher->criteria == MATCHCRITERIA_NOT_HEADERS_CONT ||
1474 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1477 } else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
1478 matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
1479 Header *header = NULL;
1481 /* address header is one of the headers we have to match when checking
1482 for any address header or all address headers? */
1483 header = procheader_parse_header(buf);
1485 (procheader_headername_equal(header->name, "From") ||
1486 procheader_headername_equal(header->name, "To") ||
1487 procheader_headername_equal(header->name, "Cc") ||
1488 procheader_headername_equal(header->name, "Reply-To") ||
1489 procheader_headername_equal(header->name, "Sender"))) {
1491 if (strcasecmp(matcher->header, "Any") == 0)
1493 else if (strcasecmp(matcher->header, "All") == 0)
1498 /* further call to matcherprop_match_one_header() can't match
1499 and it irrelevant, so: don't alter the match result */
1504 /* ZERO line must NOT match for the rule to match.
1506 if (match == MATCH_ALL) {
1507 if (matcherprop_match_one_header(matcher, buf)) {
1508 matcher->result = TRUE;
1510 matcher->result = FALSE;
1511 matcher->done = TRUE;
1513 /* else, just one line matching is enough for the rule to match
1515 } else if (matcherprop_criteria_headers(matcher) ||
1516 matcherprop_criteria_message(matcher)) {
1517 if (matcherprop_match_one_header(matcher, buf)) {
1518 matcher->result = TRUE;
1519 matcher->done = TRUE;
1523 /* if the rule matched and the matchers are OR, no need to
1524 * check the others */
1525 if (matcher->result && matcher->done) {
1526 if (!matchers->bool_and)
1536 *\brief Check if a matcher wants to check the message body
1538 *\param matcher Matcher structure
1540 *\return gboolean TRUE if body must be matched.
1542 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
1544 switch (matcher->criteria) {
1545 case MATCHCRITERIA_BODY_PART:
1546 case MATCHCRITERIA_NOT_BODY_PART:
1553 static gboolean matcherlist_match_binary_content(MatcherList *matchers, MimeInfo *partinfo)
1556 gchar buf[BUFFSIZE];
1559 if (!partinfo || partinfo->type == MIMETYPE_TEXT)
1562 outfp = procmime_get_binary_content(partinfo);
1567 while (fgets(buf, sizeof(buf), outfp) != NULL) {
1570 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1571 MatcherProp *matcher = (MatcherProp *) l->data;
1576 /* Don't scan non-text parts when looking in body, only
1577 * when looking in whole message
1579 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1580 matcher->criteria == MATCHCRITERIA_BODY_PART)
1583 /* if the criteria is ~body_part or ~message, ZERO lines
1584 * must match for the rule to match.
1586 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1587 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1588 if (matcherprop_string_match(matcher, buf,
1589 context_str[CONTEXT_BODY_LINE])) {
1590 matcher->result = FALSE;
1591 matcher->done = TRUE;
1593 matcher->result = TRUE;
1594 /* else, just one line has to match */
1595 } else if (matcherprop_criteria_body(matcher) ||
1596 matcherprop_criteria_message(matcher)) {
1597 if (matcherprop_string_match(matcher, buf,
1598 context_str[CONTEXT_BODY_LINE])) {
1599 matcher->result = TRUE;
1600 matcher->done = TRUE;
1604 /* if the matchers are OR'ed and the rule matched,
1605 * no need to check the others. */
1606 if (matcher->result && matcher->done) {
1607 if (!matchers->bool_and) {
1619 static gboolean match_content_cb(const gchar *buf, gpointer data)
1621 MatcherList *matchers = (MatcherList *)data;
1622 gboolean all_done = TRUE;
1625 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1626 MatcherProp *matcher = (MatcherProp *) l->data;
1631 /* if the criteria is ~body_part or ~message, ZERO lines
1632 * must match for the rule to match.
1634 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1635 matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1636 if (matcherprop_string_match(matcher, buf,
1637 context_str[CONTEXT_BODY_LINE])) {
1638 matcher->result = FALSE;
1639 matcher->done = TRUE;
1641 matcher->result = TRUE;
1642 /* else, just one line has to match */
1643 } else if (matcherprop_criteria_body(matcher) ||
1644 matcherprop_criteria_message(matcher)) {
1645 if (matcherprop_string_match(matcher, buf,
1646 context_str[CONTEXT_BODY_LINE])) {
1647 matcher->result = TRUE;
1648 matcher->done = TRUE;
1652 /* if the matchers are OR'ed and the rule matched,
1653 * no need to check the others. */
1654 if (matcher->result && matcher->done) {
1655 if (!matchers->bool_and) {
1666 static gboolean matcherlist_match_text_content(MatcherList *matchers, MimeInfo *partinfo)
1668 if (partinfo->type != MIMETYPE_TEXT)
1671 return procmime_scan_text_content(partinfo, match_content_cb, matchers);
1675 *\brief Check if a line in a message file's body matches
1678 *\param matchers List of conditions
1679 *\param fp Message file
1681 *\return gboolean TRUE if succesful match
1683 static gboolean matcherlist_match_body(MatcherList *matchers, gboolean body_only, MsgInfo *info)
1685 MimeInfo *mimeinfo = NULL;
1686 MimeInfo *partinfo = NULL;
1687 gboolean first_text_found = FALSE;
1689 cm_return_val_if_fail(info != NULL, FALSE);
1691 mimeinfo = procmime_scan_message(info);
1694 partinfo = procmime_mimeinfo_next(mimeinfo);
1696 for (; partinfo != NULL; partinfo = procmime_mimeinfo_next(partinfo)) {
1698 if (partinfo->type != MIMETYPE_TEXT && body_only)
1701 if (partinfo->type == MIMETYPE_TEXT) {
1702 first_text_found = TRUE;
1703 if (matcherlist_match_text_content(matchers, partinfo)) {
1704 procmime_mimeinfo_free_all(mimeinfo);
1707 } else if (matcherlist_match_binary_content(matchers, partinfo)) {
1708 procmime_mimeinfo_free_all(mimeinfo);
1712 if (body_only && first_text_found)
1715 procmime_mimeinfo_free_all(mimeinfo);
1721 *\brief Check if a message file matches criteria
1723 *\param matchers Criteria
1724 *\param info Message info
1725 *\param result Default result
1727 *\return gboolean TRUE if matched
1729 static gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
1732 gboolean read_headers;
1739 /* file need to be read ? */
1741 read_headers = FALSE;
1744 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1745 MatcherProp *matcher = (MatcherProp *) l->data;
1747 if (matcherprop_criteria_headers(matcher))
1748 read_headers = TRUE;
1749 if (matcherprop_criteria_body(matcher))
1751 if (matcherprop_criteria_message(matcher)) {
1752 read_headers = TRUE;
1756 matcher->result = FALSE;
1757 matcher->done = FALSE;
1760 if (!read_headers && !read_body)
1763 file = procmsg_get_message_file_full(info, read_headers, read_body);
1767 if ((fp = g_fopen(file, "rb")) == NULL) {
1768 FILE_OP_ERROR(file, "fopen");
1773 /* read the headers */
1776 if (matcherlist_match_headers(matchers, fp))
1779 matcherlist_skip_headers(fp);
1784 matcherlist_match_body(matchers, body_only, info);
1787 for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
1788 MatcherProp *matcher = (MatcherProp *) l->data;
1790 if (matcherprop_criteria_headers(matcher) ||
1791 matcherprop_criteria_body(matcher) ||
1792 matcherprop_criteria_message(matcher)) {
1793 if (matcher->result) {
1794 if (!matchers->bool_and) {
1800 if (matchers->bool_and) {
1816 *\brief Test list of conditions on a message.
1818 *\param matchers List of conditions
1819 *\param info Message info
1821 *\return gboolean TRUE if matched
1823 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
1831 if (matchers->bool_and)
1836 /* test the cached elements */
1838 for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
1839 MatcherProp *matcher = (MatcherProp *) l->data;
1841 if (debug_filtering_session) {
1842 gchar *buf = matcherprop_to_string(matcher);
1843 log_print(LOG_DEBUG_FILTERING, _("checking if message matches [ %s ]\n"), buf);
1847 switch(matcher->criteria) {
1848 case MATCHCRITERIA_ALL:
1849 case MATCHCRITERIA_UNREAD:
1850 case MATCHCRITERIA_NOT_UNREAD:
1851 case MATCHCRITERIA_NEW:
1852 case MATCHCRITERIA_NOT_NEW:
1853 case MATCHCRITERIA_MARKED:
1854 case MATCHCRITERIA_NOT_MARKED:
1855 case MATCHCRITERIA_DELETED:
1856 case MATCHCRITERIA_NOT_DELETED:
1857 case MATCHCRITERIA_REPLIED:
1858 case MATCHCRITERIA_NOT_REPLIED:
1859 case MATCHCRITERIA_FORWARDED:
1860 case MATCHCRITERIA_NOT_FORWARDED:
1861 case MATCHCRITERIA_LOCKED:
1862 case MATCHCRITERIA_NOT_LOCKED:
1863 case MATCHCRITERIA_SPAM:
1864 case MATCHCRITERIA_NOT_SPAM:
1865 case MATCHCRITERIA_HAS_ATTACHMENT:
1866 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
1867 case MATCHCRITERIA_SIGNED:
1868 case MATCHCRITERIA_NOT_SIGNED:
1869 case MATCHCRITERIA_COLORLABEL:
1870 case MATCHCRITERIA_NOT_COLORLABEL:
1871 case MATCHCRITERIA_IGNORE_THREAD:
1872 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1873 case MATCHCRITERIA_WATCH_THREAD:
1874 case MATCHCRITERIA_NOT_WATCH_THREAD:
1875 case MATCHCRITERIA_SUBJECT:
1876 case MATCHCRITERIA_NOT_SUBJECT:
1877 case MATCHCRITERIA_FROM:
1878 case MATCHCRITERIA_NOT_FROM:
1879 case MATCHCRITERIA_TO:
1880 case MATCHCRITERIA_NOT_TO:
1881 case MATCHCRITERIA_CC:
1882 case MATCHCRITERIA_NOT_CC:
1883 case MATCHCRITERIA_TO_OR_CC:
1884 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1885 case MATCHCRITERIA_TAG:
1886 case MATCHCRITERIA_NOT_TAG:
1887 case MATCHCRITERIA_TAGGED:
1888 case MATCHCRITERIA_NOT_TAGGED:
1889 case MATCHCRITERIA_AGE_GREATER:
1890 case MATCHCRITERIA_AGE_LOWER:
1891 case MATCHCRITERIA_AGE_GREATER_HOURS:
1892 case MATCHCRITERIA_AGE_LOWER_HOURS:
1893 case MATCHCRITERIA_NEWSGROUPS:
1894 case MATCHCRITERIA_NOT_NEWSGROUPS:
1895 case MATCHCRITERIA_INREPLYTO:
1896 case MATCHCRITERIA_NOT_INREPLYTO:
1897 case MATCHCRITERIA_REFERENCES:
1898 case MATCHCRITERIA_NOT_REFERENCES:
1899 case MATCHCRITERIA_SCORE_GREATER:
1900 case MATCHCRITERIA_SCORE_LOWER:
1901 case MATCHCRITERIA_SCORE_EQUAL:
1902 case MATCHCRITERIA_SIZE_GREATER:
1903 case MATCHCRITERIA_SIZE_SMALLER:
1904 case MATCHCRITERIA_SIZE_EQUAL:
1905 case MATCHCRITERIA_TEST:
1906 case MATCHCRITERIA_NOT_TEST:
1907 case MATCHCRITERIA_PARTIAL:
1908 case MATCHCRITERIA_NOT_PARTIAL:
1909 if (matcherprop_match(matcher, info)) {
1910 if (!matchers->bool_and) {
1911 if (debug_filtering_session)
1912 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1917 if (matchers->bool_and) {
1918 if (debug_filtering_session)
1919 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1926 /* test the condition on the file */
1928 if (matcherlist_match_file(matchers, info, result)) {
1929 if (!matchers->bool_and) {
1930 if (debug_filtering_session)
1931 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1935 if (matchers->bool_and) {
1936 if (debug_filtering_session)
1937 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1942 if (debug_filtering_session) {
1944 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1946 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1952 static gint quote_filter_str(gchar * result, guint size,
1962 for(p = path ; * p != '\0' ; p ++) {
1964 if ((* p != '\"') && (* p != '\\')) {
1965 if (remaining > 0) {
1971 result[size - 1] = '\0';
1976 if (remaining >= 2) {
1984 result[size - 1] = '\0';
1989 if (remaining > 0) {
1993 result[size - 1] = '\0';
2001 gchar * matcher_quote_str(const gchar * src)
2006 len = strlen(src) * 2 + 1;
2007 res = g_malloc(len);
2008 quote_filter_str(res, len, src);
2014 *\brief Convert a matcher structure to a string
2016 *\param matcher Matcher structure
2018 *\return gchar * Newly allocated string
2020 gchar *matcherprop_to_string(MatcherProp *matcher)
2022 gchar *matcher_str = NULL;
2023 const gchar *criteria_str;
2024 const gchar *matchtype_str;
2026 gchar * quoted_expr;
2027 gchar * quoted_header;
2029 criteria_str = NULL;
2030 for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
2031 if (matchparser_tab[i].id == matcher->criteria)
2032 criteria_str = matchparser_tab[i].str;
2034 if (criteria_str == NULL)
2037 switch (matcher->criteria) {
2038 case MATCHCRITERIA_AGE_GREATER:
2039 case MATCHCRITERIA_AGE_LOWER:
2040 case MATCHCRITERIA_AGE_GREATER_HOURS:
2041 case MATCHCRITERIA_AGE_LOWER_HOURS:
2042 case MATCHCRITERIA_SCORE_GREATER:
2043 case MATCHCRITERIA_SCORE_LOWER:
2044 case MATCHCRITERIA_SCORE_EQUAL:
2045 case MATCHCRITERIA_SIZE_GREATER:
2046 case MATCHCRITERIA_SIZE_SMALLER:
2047 case MATCHCRITERIA_SIZE_EQUAL:
2048 case MATCHCRITERIA_COLORLABEL:
2049 case MATCHCRITERIA_NOT_COLORLABEL:
2050 return g_strdup_printf("%s %i", criteria_str, matcher->value);
2051 case MATCHCRITERIA_ALL:
2052 case MATCHCRITERIA_UNREAD:
2053 case MATCHCRITERIA_NOT_UNREAD:
2054 case MATCHCRITERIA_NEW:
2055 case MATCHCRITERIA_NOT_NEW:
2056 case MATCHCRITERIA_MARKED:
2057 case MATCHCRITERIA_NOT_MARKED:
2058 case MATCHCRITERIA_DELETED:
2059 case MATCHCRITERIA_NOT_DELETED:
2060 case MATCHCRITERIA_REPLIED:
2061 case MATCHCRITERIA_NOT_REPLIED:
2062 case MATCHCRITERIA_FORWARDED:
2063 case MATCHCRITERIA_NOT_FORWARDED:
2064 case MATCHCRITERIA_LOCKED:
2065 case MATCHCRITERIA_NOT_LOCKED:
2066 case MATCHCRITERIA_SPAM:
2067 case MATCHCRITERIA_NOT_SPAM:
2068 case MATCHCRITERIA_HAS_ATTACHMENT:
2069 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
2070 case MATCHCRITERIA_SIGNED:
2071 case MATCHCRITERIA_NOT_SIGNED:
2072 case MATCHCRITERIA_PARTIAL:
2073 case MATCHCRITERIA_NOT_PARTIAL:
2074 case MATCHCRITERIA_IGNORE_THREAD:
2075 case MATCHCRITERIA_NOT_IGNORE_THREAD:
2076 case MATCHCRITERIA_WATCH_THREAD:
2077 case MATCHCRITERIA_NOT_WATCH_THREAD:
2078 case MATCHCRITERIA_TAGGED:
2079 case MATCHCRITERIA_NOT_TAGGED:
2080 return g_strdup(criteria_str);
2081 case MATCHCRITERIA_TEST:
2082 case MATCHCRITERIA_NOT_TEST:
2083 quoted_expr = matcher_quote_str(matcher->expr);
2084 matcher_str = g_strdup_printf("%s \"%s\"",
2085 criteria_str, quoted_expr);
2086 g_free(quoted_expr);
2088 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
2089 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
2090 quoted_header = matcher_quote_str(matcher->header);
2091 quoted_expr = matcher_quote_str(matcher->expr);
2092 matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
2093 criteria_str, quoted_header, quoted_expr);
2094 g_free(quoted_header);
2095 g_free(quoted_expr);
2099 matchtype_str = NULL;
2100 for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
2101 if (matchparser_tab[i].id == matcher->matchtype)
2102 matchtype_str = matchparser_tab[i].str;
2105 if (matchtype_str == NULL)
2108 switch (matcher->matchtype) {
2109 case MATCHTYPE_MATCH:
2110 case MATCHTYPE_MATCHCASE:
2111 case MATCHTYPE_REGEXP:
2112 case MATCHTYPE_REGEXPCASE:
2113 quoted_expr = matcher_quote_str(matcher->expr);
2114 if (matcher->header) {
2115 quoted_header = matcher_quote_str(matcher->header);
2116 matcher_str = g_strdup_printf
2117 ("%s \"%s\" %s \"%s\"",
2118 criteria_str, quoted_header,
2119 matchtype_str, quoted_expr);
2120 g_free(quoted_header);
2123 matcher_str = g_strdup_printf
2124 ("%s %s \"%s\"", criteria_str,
2125 matchtype_str, quoted_expr);
2126 g_free(quoted_expr);
2134 *\brief Convert a list of conditions to a string
2136 *\param matchers List of conditions
2138 *\return gchar * Newly allocated string
2140 gchar *matcherlist_to_string(const MatcherList *matchers)
2146 gchar *result = NULL;
2148 count = g_slist_length(matchers->matchers);
2149 vstr = g_new(gchar *, count + 1);
2151 for (l = matchers->matchers, cur_str = vstr; l != NULL;
2152 l = g_slist_next(l), cur_str ++) {
2153 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
2154 if (*cur_str == NULL)
2159 if (matchers->bool_and)
2160 result = g_strjoinv(" & ", vstr);
2162 result = g_strjoinv(" | ", vstr);
2164 for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
2172 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
2173 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
2175 static void add_str_default(gchar ** dest,
2176 const gchar * s, const gchar * d)
2178 gchar quoted_str[4096];
2186 quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
2187 strcpy(* dest, quoted_str);
2189 (* dest) += strlen(* dest);
2192 /* matching_build_command() - preferably cmd should be unescaped */
2194 *\brief Build the command-line to execute
2196 *\param cmd String with command-line specifiers
2197 *\param info Message info to use for command
2199 *\return gchar * Newly allocated string
2201 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
2203 const gchar *s = cmd;
2204 gchar *filename = NULL;
2205 gchar *processed_cmd;
2209 const gchar *const no_subject = _("(none)") ;
2210 const gchar *const no_from = _("(none)") ;
2211 const gchar *const no_to = _("(none)") ;
2212 const gchar *const no_cc = _("(none)") ;
2213 const gchar *const no_date = _("(none)") ;
2214 const gchar *const no_msgid = _("(none)") ;
2215 const gchar *const no_newsgroups = _("(none)") ;
2216 const gchar *const no_references = _("(none)") ;
2218 size = STRLEN_ZERO(cmd) + 1;
2219 while (*s != '\0') {
2226 case 's': /* subject */
2227 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
2229 case 'f': /* from */
2230 size += STRLEN_DEFAULT(info->from, no_from) - 2;
2233 size += STRLEN_DEFAULT(info->to, no_to) - 2;
2236 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
2238 case 'd': /* date */
2239 size += STRLEN_DEFAULT(info->date, no_date) - 2;
2241 case 'i': /* message-id */
2242 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
2244 case 'n': /* newsgroups */
2245 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
2247 case 'r': /* references */
2248 /* FIXME: using the inreplyto header for reference */
2249 size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
2251 case 'F': /* file */
2252 if (filename == NULL)
2253 filename = folder_item_fetch_msg(info->folder, info->msgnum);
2255 if (filename == NULL) {
2256 g_warning("filename is not set");
2260 size += strlen(filename) - 2;
2269 /* as the string can be quoted, we double the result */
2272 processed_cmd = g_new0(gchar, size);
2276 while (*s != '\0') {
2284 case 's': /* subject */
2285 add_str_default(&p, info->subject,
2288 case 'f': /* from */
2289 add_str_default(&p, info->from,
2293 add_str_default(&p, info->to,
2297 add_str_default(&p, info->cc,
2300 case 'd': /* date */
2301 add_str_default(&p, info->date,
2304 case 'i': /* message-id */
2305 add_str_default(&p, info->msgid,
2308 case 'n': /* newsgroups */
2309 add_str_default(&p, info->newsgroups,
2312 case 'r': /* references */
2313 /* FIXME: using the inreplyto header for references */
2314 add_str_default(&p, info->inreplyto, no_references);
2316 case 'F': /* file */
2317 if (filename != NULL)
2318 add_str_default(&p, filename, NULL);
2337 return processed_cmd;
2339 #undef STRLEN_DEFAULT
2342 /* ************************************************************ */
2346 *\brief Write filtering list to file
2349 *\param prefs_filtering List of filtering conditions
2351 static int prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
2355 for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
2356 gchar *filtering_str = NULL;
2357 gchar *tmp_name = NULL;
2358 FilteringProp *prop = NULL;
2360 if (NULL == (prop = (FilteringProp *) cur->data))
2363 if (NULL == (filtering_str = filteringprop_to_string(prop)))
2366 if (prop->enabled) {
2367 if (fputs("enabled ", fp) == EOF) {
2368 FILE_OP_ERROR("filtering config", "fputs");
2372 if (fputs("disabled ", fp) == EOF) {
2373 FILE_OP_ERROR("filtering config", "fputs");
2378 if (fputs("rulename \"", fp) == EOF) {
2379 FILE_OP_ERROR("filtering config", "fputs");
2380 g_free(filtering_str);
2383 tmp_name = prop->name;
2384 while (tmp_name && *tmp_name != '\0') {
2385 if (*tmp_name != '"') {
2386 if (fputc(*tmp_name, fp) == EOF) {
2387 FILE_OP_ERROR("filtering config", "fputs || fputc");
2388 g_free(filtering_str);
2391 } else if (*tmp_name == '"') {
2392 if (fputc('\\', fp) == EOF ||
2393 fputc('"', fp) == EOF) {
2394 FILE_OP_ERROR("filtering config", "fputs || fputc");
2395 g_free(filtering_str);
2401 if (fputs("\" ", fp) == EOF) {
2402 FILE_OP_ERROR("filtering config", "fputs");
2403 g_free(filtering_str);
2407 if (prop->account_id != 0) {
2410 tmp = g_strdup_printf("account %d ", prop->account_id);
2411 if (fputs(tmp, fp) == EOF) {
2412 FILE_OP_ERROR("filtering config", "fputs");
2419 if(fputs(filtering_str, fp) == EOF ||
2420 fputc('\n', fp) == EOF) {
2421 FILE_OP_ERROR("filtering config", "fputs || fputc");
2422 g_free(filtering_str);
2425 g_free(filtering_str);
2431 typedef struct _NodeLoopData {
2437 *\brief Write matchers from a folder item
2439 *\param node Node with folder info
2440 *\param data File pointer
2442 *\return gboolean FALSE
2444 static gboolean prefs_matcher_write_func(GNode *node, gpointer d)
2447 NodeLoopData *data = (NodeLoopData *)d;
2449 GSList *prefs_filtering;
2452 /* prevent warning */
2453 if (item->path == NULL)
2455 id = folder_item_get_identifier(item);
2458 prefs_filtering = item->prefs->processing;
2460 if (prefs_filtering != NULL) {
2461 if (fprintf(data->fp, "[%s]\n", id) < 0) {
2465 if (prefs_filtering_write(data->fp, prefs_filtering) < 0) {
2469 if (fputc('\n', data->fp) == EOF) {
2481 *\brief Save matchers from folder items
2485 static int prefs_matcher_save(FILE *fp)
2493 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
2496 folder = (Folder *) cur->data;
2497 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2498 prefs_matcher_write_func, &data);
2501 if (data.error == TRUE)
2504 /* pre global rules */
2505 if (fprintf(fp, "[preglobal]\n") < 0 ||
2506 prefs_filtering_write(fp, pre_global_processing) < 0 ||
2507 fputc('\n', fp) == EOF)
2510 /* post global rules */
2511 if (fprintf(fp, "[postglobal]\n") < 0 ||
2512 prefs_filtering_write(fp, post_global_processing) < 0 ||
2513 fputc('\n', fp) == EOF)
2516 /* filtering rules */
2517 if (fprintf(fp, "[filtering]\n") < 0 ||
2518 prefs_filtering_write(fp, filtering_rules) < 0 ||
2519 fputc('\n', fp) == EOF)
2526 *\brief Write filtering / matcher configuration file
2528 void prefs_matcher_write_config(void)
2533 debug_print("Writing matcher configuration...\n");
2535 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2538 if ((pfile = prefs_write_open(rcpath)) == NULL) {
2539 g_warning("failed to write configuration to file\n");
2546 if (prefs_matcher_save(pfile->fp) < 0) {
2547 g_warning("failed to write configuration to file\n");
2548 prefs_file_close_revert(pfile);
2549 } else if (prefs_file_close(pfile) < 0) {
2550 g_warning("failed to save configuration to file\n");
2554 /* ******************************************************************* */
2556 static void matcher_add_rulenames(const gchar *rcpath)
2558 gchar *newpath = g_strconcat(rcpath, ".new", NULL);
2559 FILE *src = g_fopen(rcpath, "rb");
2560 FILE *dst = g_fopen(newpath, "wb");
2561 gchar buf[BUFFSIZE];
2579 while (fgets (buf, sizeof(buf), src) != NULL) {
2580 if (strlen(buf) > 2 && buf[0] != '['
2581 && strncmp(buf, "rulename \"", 10)
2582 && strncmp(buf, "enabled rulename \"", 18)
2583 && strncmp(buf, "disabled rulename \"", 18)) {
2584 r = fwrite("enabled rulename \"\" ",
2585 strlen("enabled rulename \"\" "), 1, dst);
2587 g_message("cannot fwrite rulename\n");
2590 r = fwrite(buf, strlen(buf), 1, dst);
2592 g_message("cannot fwrite rule\n");
2597 move_file(newpath, rcpath, TRUE);
2602 *\brief Read matcher configuration
2604 void prefs_matcher_read_config(void)
2607 gchar *rc_old_format;
2610 create_matchparser_hashtab();
2611 prefs_filtering_clear();
2613 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
2614 rc_old_format = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC,
2615 ".pre_names", NULL);
2617 if (!is_file_exist(rc_old_format) && is_file_exist(rcpath)) {
2618 /* backup file with no rules names, in case
2619 * anything goes wrong */
2620 copy_file(rcpath, rc_old_format, FALSE);
2621 /* now hack the file in order to have it to the new format */
2622 matcher_add_rulenames(rcpath);
2625 g_free(rc_old_format);
2627 f = g_fopen(rcpath, "rb");
2631 matcher_parser_start_parsing(f);
2632 fclose(matcher_parserin);
2635 /* previous version compatibility */
2637 /* g_print("reading filtering\n"); */
2638 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2639 FILTERING_RC, NULL);
2640 f = g_fopen(rcpath, "rb");
2644 matcher_parser_start_parsing(f);
2645 fclose(matcher_parserin);
2648 /* g_print("reading scoring\n"); */
2649 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2651 f = g_fopen(rcpath, "rb");
2655 matcher_parser_start_parsing(f);
2656 fclose(matcher_parserin);