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_MESSAGEID
, "messageid"},
118 {MATCHCRITERIA_NOT_MESSAGEID
, "~messageid"},
119 {MATCHCRITERIA_INREPLYTO
, "inreplyto"},
120 {MATCHCRITERIA_NOT_INREPLYTO
, "~inreplyto"},
121 {MATCHCRITERIA_REFERENCES
, "references"},
122 {MATCHCRITERIA_NOT_REFERENCES
, "~references"},
123 {MATCHCRITERIA_SCORE_GREATER
, "score_greater"},
124 {MATCHCRITERIA_SCORE_LOWER
, "score_lower"},
125 {MATCHCRITERIA_SCORE_EQUAL
, "score_equal"},
126 {MATCHCRITERIA_PARTIAL
, "partial"},
127 {MATCHCRITERIA_NOT_PARTIAL
, "~partial"},
128 {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
, "found_in_addressbook"},
129 {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
, "~found_in_addressbook"},
131 {MATCHCRITERIA_SIZE_GREATER
, "size_greater"},
132 {MATCHCRITERIA_SIZE_SMALLER
, "size_smaller"},
133 {MATCHCRITERIA_SIZE_EQUAL
, "size_equal"},
135 /* content have to be read */
136 {MATCHCRITERIA_HEADER
, "header"},
137 {MATCHCRITERIA_NOT_HEADER
, "~header"},
138 {MATCHCRITERIA_HEADERS_PART
, "headers_part"},
139 {MATCHCRITERIA_NOT_HEADERS_PART
, "~headers_part"},
140 {MATCHCRITERIA_HEADERS_CONT
, "headers_cont"},
141 {MATCHCRITERIA_NOT_HEADERS_CONT
, "~headers_cont"},
142 {MATCHCRITERIA_MESSAGE
, "message"},
143 {MATCHCRITERIA_NOT_MESSAGE
, "~message"},
144 {MATCHCRITERIA_BODY_PART
, "body_part"},
145 {MATCHCRITERIA_NOT_BODY_PART
, "~body_part"},
146 {MATCHCRITERIA_TEST
, "test"},
147 {MATCHCRITERIA_NOT_TEST
, "~test"},
150 {MATCHTYPE_MATCHCASE
, "matchcase"},
151 {MATCHTYPE_MATCH
, "match"},
152 {MATCHTYPE_REGEXPCASE
, "regexpcase"},
153 {MATCHTYPE_REGEXP
, "regexp"},
156 {MATCHACTION_SCORE
, "score"}, /* for backward compatibility */
157 {MATCHACTION_MOVE
, "move"},
158 {MATCHACTION_COPY
, "copy"},
159 {MATCHACTION_DELETE
, "delete"},
160 {MATCHACTION_MARK
, "mark"},
161 {MATCHACTION_UNMARK
, "unmark"},
162 {MATCHACTION_LOCK
, "lock"},
163 {MATCHACTION_UNLOCK
, "unlock"},
164 {MATCHACTION_MARK_AS_READ
, "mark_as_read"},
165 {MATCHACTION_MARK_AS_UNREAD
, "mark_as_unread"},
166 {MATCHACTION_MARK_AS_SPAM
, "mark_as_spam"},
167 {MATCHACTION_MARK_AS_HAM
, "mark_as_ham"},
168 {MATCHACTION_FORWARD
, "forward"},
169 {MATCHACTION_FORWARD_AS_ATTACHMENT
, "forward_as_attachment"},
170 {MATCHACTION_EXECUTE
, "execute"},
171 {MATCHACTION_COLOR
, "color"},
172 {MATCHACTION_REDIRECT
, "redirect"},
173 {MATCHACTION_CHANGE_SCORE
, "change_score"},
174 {MATCHACTION_SET_SCORE
, "set_score"},
175 {MATCHACTION_STOP
, "stop"},
176 {MATCHACTION_HIDE
, "hide"},
177 {MATCHACTION_IGNORE
, "ignore"},
178 {MATCHACTION_WATCH
, "watch"},
179 {MATCHACTION_ADD_TO_ADDRESSBOOK
, "add_to_addressbook"},
180 {MATCHACTION_SET_TAG
, "set_tag"},
181 {MATCHACTION_UNSET_TAG
, "unset_tag"},
182 {MATCHACTION_CLEAR_TAGS
, "clear_tags"},
207 static gchar
*context_str
[N_CONTEXT_STRS
];
209 void matcher_init(void)
211 if (context_str
[CONTEXT_SUBJECT
] != NULL
)
214 context_str
[CONTEXT_SUBJECT
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Subject:"));
215 context_str
[CONTEXT_FROM
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
216 context_str
[CONTEXT_TO
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
217 context_str
[CONTEXT_CC
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
218 context_str
[CONTEXT_NEWSGROUPS
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Newsgroups:"));
219 context_str
[CONTEXT_MESSAGEID
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Message-ID:"));
220 context_str
[CONTEXT_IN_REPLY_TO
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("In-Reply-To:"));
221 context_str
[CONTEXT_REFERENCES
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("References:"));
222 context_str
[CONTEXT_HEADER
] = g_strdup(_("header"));
223 context_str
[CONTEXT_HEADER_LINE
] = g_strdup(_("header line"));
224 context_str
[CONTEXT_BODY_LINE
] = g_strdup(_("body line"));
225 context_str
[CONTEXT_TAG
] = g_strdup(_("tag"));
228 void matcher_done(void)
231 for (i
= 0; i
< N_CONTEXT_STRS
; i
++) {
232 g_free(context_str
[i
]);
233 context_str
[i
] = NULL
;
237 extern gboolean debug_filtering_session
;
240 *\brief Look up table with keywords defined in \sa matchparser_tab
242 static GHashTable
*matchparser_hashtab
;
245 *\brief Translate keyword id to keyword string
247 *\param id Id of keyword
249 *\return const gchar * Keyword
251 const gchar
*get_matchparser_tab_str(gint id
)
255 for (i
= 0; i
< sizeof matchparser_tab
/ sizeof matchparser_tab
[0]; i
++) {
256 if (matchparser_tab
[i
].id
== id
)
257 return matchparser_tab
[i
].str
;
263 *\brief Create keyword lookup table
265 static void create_matchparser_hashtab(void)
269 if (matchparser_hashtab
) return;
270 matchparser_hashtab
= g_hash_table_new(g_str_hash
, g_str_equal
);
271 for (i
= 0; i
< sizeof matchparser_tab
/ sizeof matchparser_tab
[0]; i
++)
272 g_hash_table_insert(matchparser_hashtab
,
273 matchparser_tab
[i
].str
,
274 (gpointer
) &matchparser_tab
[i
]);
278 *\brief Return a keyword id from a keyword string
280 *\param str Keyword string
282 *\return gint Keyword id
284 gint
get_matchparser_tab_id(const gchar
*str
)
288 if (NULL
!= (res
= g_hash_table_lookup(matchparser_hashtab
, str
))) {
294 /* **************** data structure allocation **************** */
297 *\brief Allocate a structure for a filtering / scoring
298 * "condition" (a matcher structure)
300 *\param criteria Criteria ID (MATCHCRITERIA_XXXX)
301 *\param header Header string (if criteria is MATCHCRITERIA_HEADER
302 or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
303 *\param matchtype Type of action (MATCHTYPE_XXX)
304 *\param expr String value or expression to check
305 *\param value Integer value to check
307 *\return MatcherProp * Pointer to newly allocated structure
309 MatcherProp
*matcherprop_new(gint criteria
, const gchar
*header
,
310 gint matchtype
, const gchar
*expr
,
315 prop
= g_new0(MatcherProp
, 1);
316 prop
->criteria
= criteria
;
317 prop
->header
= header
!= NULL
? g_strdup(header
) : NULL
;
319 prop
->expr
= expr
!= NULL
? g_strdup(expr
) : NULL
;
321 prop
->matchtype
= matchtype
;
332 *\brief Free a matcher structure
334 *\param prop Pointer to matcher structure allocated with
337 void matcherprop_free(MatcherProp
*prop
)
340 g_free(prop
->header
);
342 if (prop
->preg
!= NULL
) {
351 *\brief Copy a matcher structure
353 *\param src Matcher structure to copy
355 *\return MatcherProp * Pointer to newly allocated matcher structure
357 MatcherProp
*matcherprop_copy(const MatcherProp
*src
)
359 MatcherProp
*prop
= g_new0(MatcherProp
, 1);
361 prop
->criteria
= src
->criteria
;
362 prop
->header
= src
->header
? g_strdup(src
->header
) : NULL
;
363 prop
->expr
= src
->expr
? g_strdup(src
->expr
) : NULL
;
364 prop
->matchtype
= src
->matchtype
;
367 prop
->preg
= NULL
; /* will be re-evaluated */
369 prop
->value
= src
->value
;
370 prop
->error
= src
->error
;
374 /* ************** match ******************************/
376 static gboolean match_with_addresses_in_addressbook
377 (MatcherProp
*prop
, GSList
*address_list
, gint type
,
378 gchar
* folderpath
, gint match
)
381 gboolean found
= FALSE
;
384 cm_return_val_if_fail(address_list
!= NULL
, FALSE
);
386 debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
387 g_slist_length(address_list
), folderpath
?folderpath
:"(null)");
389 if (folderpath
== NULL
||
390 strcasecmp(folderpath
, "Any") == 0 ||
396 start_address_completion(path
);
398 for (walk
= address_list
; walk
!= NULL
; walk
= walk
->next
) {
399 /* exact matching of email address */
400 guint num_addr
= complete_address(walk
->data
);
403 /* skip first item (this is the search string itself) */
405 for (; i
< num_addr
&& !found
; i
++) {
406 gchar
*addr
= get_complete_address(i
);
407 extract_address(addr
);
408 if (strcasecmp(addr
, walk
->data
) == 0) {
412 if (debug_filtering_session
413 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
414 log_print(LOG_DEBUG_FILTERING
,
415 "address [ %s ] matches\n",
416 (gchar
*)walk
->data
);
423 if (debug_filtering_session
424 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
426 log_print(LOG_DEBUG_FILTERING
,
427 "address [ %s ] does NOT match\n",
428 (gchar
*)walk
->data
);
432 if (match
== MATCH_ALL
) {
433 /* if matching all addresses, stop if one doesn't match */
436 if (debug_filtering_session
437 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
438 log_print(LOG_DEBUG_FILTERING
,
439 "not all address match (matching all)\n");
443 } else if (match
== MATCH_ANY
) {
444 /* if matching any address, stop if one does match */
447 if (debug_filtering_session
448 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
449 log_print(LOG_DEBUG_FILTERING
,
450 "at least one address matches (matching any)\n");
455 /* MATCH_ONE: there should be only one loop iteration */
458 end_address_completion();
464 *\brief Find out if a string matches a condition
466 *\param prop Matcher structure
467 *\param str String to check
469 *\return gboolean TRUE if str matches the condition in the
472 static gboolean
matcherprop_string_match(MatcherProp
*prop
, const gchar
*str
,
473 const gchar
*debug_context
)
477 gboolean ret
= FALSE
;
478 gboolean should_free
= FALSE
;
482 if (prop
->matchtype
== MATCHTYPE_REGEXPCASE
||
483 prop
->matchtype
== MATCHTYPE_MATCHCASE
) {
484 str1
= g_utf8_casefold(str
, -1);
485 down_expr
= g_utf8_casefold(prop
->expr
, -1);
489 down_expr
= (gchar
*)prop
->expr
;
493 switch (prop
->matchtype
) {
494 case MATCHTYPE_REGEXPCASE
:
495 case MATCHTYPE_REGEXP
:
496 if (!prop
->preg
&& (prop
->error
== 0)) {
497 prop
->preg
= g_new0(regex_t
, 1);
498 /* if regexp then don't use the escaped string */
499 if (regcomp(prop
->preg
, down_expr
,
500 REG_NOSUB
| REG_EXTENDED
501 | ((prop
->matchtype
== MATCHTYPE_REGEXPCASE
)
502 ? REG_ICASE
: 0)) != 0) {
508 if (prop
->preg
== NULL
) {
513 if (regexec(prop
->preg
, str1
, 0, NULL
, 0) == 0)
519 if (debug_filtering_session
520 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
521 gchar
*stripped
= g_strdup(str
);
523 strretchomp(stripped
);
525 log_print(LOG_DEBUG_FILTERING
,
526 "%s value [ %s ] matches regular expression [ %s ] (%s)\n",
527 debug_context
, stripped
, prop
->expr
,
528 prop
->matchtype
== MATCHTYPE_REGEXP
? _("Case sensitive"):_("Case insensitive"));
530 log_print(LOG_DEBUG_FILTERING
,
531 "%s value [ %s ] does NOT match regular expression [ %s ] (%s)\n",
532 debug_context
, stripped
, prop
->expr
,
533 prop
->matchtype
== MATCHTYPE_REGEXP
? _("Case sensitive"):_("Case insensitive"));
538 case MATCHTYPE_MATCHCASE
:
539 case MATCHTYPE_MATCH
:
540 ret
= (strstr(str1
, down_expr
) != NULL
);
543 if (debug_filtering_session
544 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
545 gchar
*stripped
= g_strdup(str
);
547 strretchomp(stripped
);
549 log_print(LOG_DEBUG_FILTERING
,
550 "%s value [ %s ] contains [ %s ] (%s)\n",
551 debug_context
, stripped
, prop
->expr
,
552 prop
->matchtype
== MATCHTYPE_MATCH
? _("Case sensitive"):_("Case insensitive"));
554 log_print(LOG_DEBUG_FILTERING
,
555 "%s value [ %s ] does NOT contain [ %s ] (%s)\n",
556 debug_context
, stripped
, prop
->expr
,
557 prop
->matchtype
== MATCHTYPE_MATCH
? _("Case sensitive"):_("Case insensitive"));
576 *\brief Find out if a tag matches a condition
578 *\param prop Matcher structure
579 *\param msginfo message to check
581 *\return gboolean TRUE if msginfo matches the condition in the
584 static gboolean
matcherprop_tag_match(MatcherProp
*prop
, MsgInfo
*msginfo
,
585 const gchar
*debug_context
)
587 gboolean ret
= FALSE
;
590 if (msginfo
== NULL
|| msginfo
->tags
== NULL
)
593 for (cur
= msginfo
->tags
; cur
; cur
= cur
->next
) {
594 const gchar
*str
= tags_get_tag(GPOINTER_TO_INT(cur
->data
));
597 if (matcherprop_string_match(prop
, str
, debug_context
)) {
606 *\brief Find out if the string-ed list matches a condition
608 *\param prop Matcher structure
609 *\param list GSList of strings to check
611 *\return gboolean TRUE if str matches the condition in the
614 static gboolean
matcherprop_list_match(MatcherProp
*prop
, const GSList
*list
,
615 const gchar
*debug_context
)
619 for(cur
= list
; cur
!= NULL
; cur
= cur
->next
) {
620 if (matcherprop_string_match(prop
, (gchar
*)cur
->data
, debug_context
))
626 static gboolean
matcherprop_header_line_match(MatcherProp
*prop
, const gchar
*hdr
,
627 const gchar
*str
, const gboolean both
,
628 const gchar
*debug_context
)
630 gboolean res
= FALSE
;
632 if (hdr
== NULL
|| str
== NULL
)
636 /* Search in all header names and content.
638 gchar
*line
= g_strdup_printf("%s %s", hdr
, str
);
639 res
= matcherprop_string_match(prop
, line
, debug_context
);
642 /* Search only in content and exclude private headers.
643 * E.g.: searching for "H foo" in folder x/foo would return
644 * *all* mail as SCF and RMID will match.
645 * Searching for "H sent" would return all resent messages
646 * as "Resent-From: whatever" will match.
648 if (procheader_header_is_internal(hdr
))
650 res
= matcherprop_string_match(prop
, str
, debug_context
);
657 typedef struct _thread_data
{
664 static void *matcher_test_thread(void *data
)
666 thread_data
*td
= (thread_data
*)data
;
669 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE
, NULL
);
670 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS
, NULL
);
672 result
= system(td
->cmd
);
673 td
->done
= TRUE
; /* let the caller thread join() */
674 return GINT_TO_POINTER(result
);
679 *\brief Execute a command defined in the matcher structure
681 *\param prop Pointer to matcher structure
682 *\param info Pointer to message info structure
684 *\return gboolean TRUE if command was executed succesfully
686 static gboolean
matcherprop_match_test(const MatcherProp
*prop
,
695 thread_data
*td
= g_new0(thread_data
, 1);
697 time_t start_time
= time(NULL
);
700 file
= procmsg_get_message_file(info
);
709 cmd
= matching_build_command(prop
->expr
, info
);
719 if (debug_filtering_session
720 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
721 log_print(LOG_DEBUG_FILTERING
,
722 "starting threaded command [ %s ]\n",
728 if (pthread_attr_init(&pta
) != 0 ||
729 pthread_attr_setdetachstate(&pta
, PTHREAD_CREATE_JOINABLE
) != 0 ||
730 pthread_create(&pt
, &pta
, matcher_test_thread
, td
) != 0)
731 retval
= system(cmd
);
733 debug_print("waiting for test thread\n");
735 /* don't let the interface freeze while waiting */
737 if (time(NULL
) - start_time
> 30) {
743 pthread_join(pt
, &res
);
744 retval
= GPOINTER_TO_INT(res
);
745 debug_print(" test thread returned %d\n", retval
);
750 if (debug_filtering_session
751 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
752 log_print(LOG_DEBUG_FILTERING
,
753 "starting synchronous command [ %s ]\n",
757 retval
= system(cmd
);
759 debug_print("Command exit code: %d\n", retval
);
762 if (debug_filtering_session
763 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
764 log_print(LOG_DEBUG_FILTERING
,
765 "command returned [ %d ]\n",
770 return (retval
== 0);
774 *\brief Check if a message matches the condition in a matcher
777 *\param prop Pointer to matcher structure
778 *\param info Pointer to message info
780 *\return gboolean TRUE if a match
782 static gboolean
matcherprop_match(MatcherProp
*prop
,
786 gint age_mult_hours
= 1;
788 switch(prop
->criteria
) {
789 case MATCHCRITERIA_ALL
:
791 case MATCHCRITERIA_UNREAD
:
792 return MSG_IS_UNREAD(info
->flags
);
793 case MATCHCRITERIA_NOT_UNREAD
:
794 return !MSG_IS_UNREAD(info
->flags
);
795 case MATCHCRITERIA_NEW
:
796 return MSG_IS_NEW(info
->flags
);
797 case MATCHCRITERIA_NOT_NEW
:
798 return !MSG_IS_NEW(info
->flags
);
799 case MATCHCRITERIA_MARKED
:
800 return MSG_IS_MARKED(info
->flags
);
801 case MATCHCRITERIA_NOT_MARKED
:
802 return !MSG_IS_MARKED(info
->flags
);
803 case MATCHCRITERIA_DELETED
:
804 return MSG_IS_DELETED(info
->flags
);
805 case MATCHCRITERIA_NOT_DELETED
:
806 return !MSG_IS_DELETED(info
->flags
);
807 case MATCHCRITERIA_REPLIED
:
808 return MSG_IS_REPLIED(info
->flags
);
809 case MATCHCRITERIA_NOT_REPLIED
:
810 return !MSG_IS_REPLIED(info
->flags
);
811 case MATCHCRITERIA_FORWARDED
:
812 return MSG_IS_FORWARDED(info
->flags
);
813 case MATCHCRITERIA_NOT_FORWARDED
:
814 return !MSG_IS_FORWARDED(info
->flags
);
815 case MATCHCRITERIA_LOCKED
:
816 return MSG_IS_LOCKED(info
->flags
);
817 case MATCHCRITERIA_NOT_LOCKED
:
818 return !MSG_IS_LOCKED(info
->flags
);
819 case MATCHCRITERIA_SPAM
:
820 return MSG_IS_SPAM(info
->flags
);
821 case MATCHCRITERIA_NOT_SPAM
:
822 return !MSG_IS_SPAM(info
->flags
);
823 case MATCHCRITERIA_HAS_ATTACHMENT
:
824 return MSG_IS_WITH_ATTACHMENT(info
->flags
);
825 case MATCHCRITERIA_HAS_NO_ATTACHMENT
:
826 return !MSG_IS_WITH_ATTACHMENT(info
->flags
);
827 case MATCHCRITERIA_SIGNED
:
828 return MSG_IS_SIGNED(info
->flags
);
829 case MATCHCRITERIA_NOT_SIGNED
:
830 return !MSG_IS_SIGNED(info
->flags
);
831 case MATCHCRITERIA_COLORLABEL
:
833 gint color
= MSG_GET_COLORLABEL_VALUE(info
->flags
);
834 gboolean ret
= (color
== prop
->value
);
837 if (debug_filtering_session
838 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
840 log_print(LOG_DEBUG_FILTERING
,
841 "message color value [ %d ] matches color value [ %d ]\n",
844 log_print(LOG_DEBUG_FILTERING
,
845 "message color value [ %d ] does NOT match color value [ %d ]\n",
851 case MATCHCRITERIA_NOT_COLORLABEL
:
853 gint color
= MSG_GET_COLORLABEL_VALUE(info
->flags
);
854 gboolean ret
= (color
!= prop
->value
);
857 if (debug_filtering_session
858 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
860 log_print(LOG_DEBUG_FILTERING
,
861 "message color value [ %d ] matches color value [ %d ]\n",
864 log_print(LOG_DEBUG_FILTERING
,
865 "message color value [ %d ] does NOT match color value [ %d ]\n",
871 case MATCHCRITERIA_IGNORE_THREAD
:
872 return MSG_IS_IGNORE_THREAD(info
->flags
);
873 case MATCHCRITERIA_NOT_IGNORE_THREAD
:
874 return !MSG_IS_IGNORE_THREAD(info
->flags
);
875 case MATCHCRITERIA_WATCH_THREAD
:
876 return MSG_IS_WATCH_THREAD(info
->flags
);
877 case MATCHCRITERIA_NOT_WATCH_THREAD
:
878 return !MSG_IS_WATCH_THREAD(info
->flags
);
879 case MATCHCRITERIA_SUBJECT
:
880 return matcherprop_string_match(prop
, info
->subject
, context_str
[CONTEXT_SUBJECT
]);
881 case MATCHCRITERIA_NOT_SUBJECT
:
882 return !matcherprop_string_match(prop
, info
->subject
, context_str
[CONTEXT_SUBJECT
]);
883 case MATCHCRITERIA_FROM
:
884 return matcherprop_string_match(prop
, info
->from
, context_str
[CONTEXT_FROM
]);
885 case MATCHCRITERIA_NOT_FROM
:
886 return !matcherprop_string_match(prop
, info
->from
, context_str
[CONTEXT_FROM
]);
887 case MATCHCRITERIA_TO
:
888 return matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
]);
889 case MATCHCRITERIA_NOT_TO
:
890 return !matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
]);
891 case MATCHCRITERIA_CC
:
892 return matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
893 case MATCHCRITERIA_NOT_CC
:
894 return !matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
895 case MATCHCRITERIA_TO_OR_CC
:
896 return matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
])
897 || matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
898 case MATCHCRITERIA_NOT_TO_AND_NOT_CC
:
899 return !matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
])
900 && !matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
901 case MATCHCRITERIA_TAG
:
902 return matcherprop_tag_match(prop
, info
, context_str
[CONTEXT_TAG
]);
903 case MATCHCRITERIA_NOT_TAG
:
904 return !matcherprop_tag_match(prop
, info
, context_str
[CONTEXT_TAG
]);
905 case MATCHCRITERIA_TAGGED
:
906 return info
->tags
!= NULL
;
907 case MATCHCRITERIA_NOT_TAGGED
:
908 return info
->tags
== NULL
;
909 case MATCHCRITERIA_AGE_GREATER
:
911 /* Fallthrough intended */
912 case MATCHCRITERIA_AGE_GREATER_HOURS
:
918 age
= ((t
- info
->date_t
) / (60 * 60 * age_mult_hours
));
919 ret
= (age
>= prop
->value
);
922 if (debug_filtering_session
923 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
925 log_print(LOG_DEBUG_FILTERING
,
926 "message age [ %d ] is greater than [ %d ]\n",
929 log_print(LOG_DEBUG_FILTERING
,
930 "message age [ %d ] is not greater than [ %d ]\n",
936 case MATCHCRITERIA_AGE_LOWER
:
938 /* Fallthrough intended */
939 case MATCHCRITERIA_AGE_LOWER_HOURS
:
945 age
= ((t
- info
->date_t
) / (60 * 60 * age_mult_hours
));
946 ret
= (age
< 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 age [ %d ] is lower than [ %d ]\n",
956 log_print(LOG_DEBUG_FILTERING
,
957 "message age [ %d ] is not lower than [ %d ]\n",
963 case MATCHCRITERIA_SCORE_GREATER
:
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 greater than [ %d ]\n",
973 info
->score
, prop
->value
);
975 log_print(LOG_DEBUG_FILTERING
,
976 "message score [ %d ] is not greater than [ %d ]\n",
977 info
->score
, prop
->value
);
982 case MATCHCRITERIA_SCORE_LOWER
:
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 lower than [ %d ]\n",
992 info
->score
, prop
->value
);
994 log_print(LOG_DEBUG_FILTERING
,
995 "message score [ %d ] is not lower than [ %d ]\n",
996 info
->score
, prop
->value
);
1001 case MATCHCRITERIA_SCORE_EQUAL
:
1003 gboolean ret
= (info
->score
== prop
->value
);
1006 if (debug_filtering_session
1007 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1009 log_print(LOG_DEBUG_FILTERING
,
1010 "message score [ %d ] is equal to [ %d ]\n",
1011 info
->score
, prop
->value
);
1013 log_print(LOG_DEBUG_FILTERING
,
1014 "message score [ %d ] is not equal to [ %d ]\n",
1015 info
->score
, prop
->value
);
1020 case MATCHCRITERIA_SIZE_GREATER
:
1022 /* FIXME: info->size is a goffset */
1023 gboolean ret
= (info
->size
> (goffset
) prop
->value
);
1026 if (debug_filtering_session
1027 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1029 log_print(LOG_DEBUG_FILTERING
,
1030 "message size is greater than [ %d ]\n",
1033 log_print(LOG_DEBUG_FILTERING
,
1034 "message size is not greater than [ %d ]\n",
1040 case MATCHCRITERIA_SIZE_SMALLER
:
1042 /* FIXME: info->size is a goffset */
1043 gboolean ret
= (info
->size
< (goffset
) prop
->value
);
1046 if (debug_filtering_session
1047 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1049 log_print(LOG_DEBUG_FILTERING
,
1050 "message size is smaller than [ %d ]\n",
1053 log_print(LOG_DEBUG_FILTERING
,
1054 "message size is not smaller than [ %d ]\n",
1060 case MATCHCRITERIA_SIZE_EQUAL
:
1062 /* FIXME: info->size is a goffset */
1063 gboolean ret
= (info
->size
== (goffset
) prop
->value
);
1066 if (debug_filtering_session
1067 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1069 log_print(LOG_DEBUG_FILTERING
,
1070 "message size is equal to [ %d ]\n",
1073 log_print(LOG_DEBUG_FILTERING
,
1074 "message size is not equal to [ %d ]\n",
1080 case MATCHCRITERIA_PARTIAL
:
1082 /* FIXME: info->size is a goffset */
1083 gboolean ret
= (info
->total_size
!= 0 && info
->size
!= (goffset
)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 partially downloaded, size is less than total size [ %d ])\n",
1093 log_print(LOG_DEBUG_FILTERING
,
1094 "message is not partially downloaded\n");
1099 case MATCHCRITERIA_NOT_PARTIAL
:
1101 /* FIXME: info->size is a goffset */
1102 gboolean ret
= (info
->total_size
== 0 || info
->size
== (goffset
)info
->total_size
);
1105 if (debug_filtering_session
1106 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1108 log_print(LOG_DEBUG_FILTERING
,
1109 "message is not partially downloaded\n");
1111 log_print(LOG_DEBUG_FILTERING
,
1112 "message is partially downloaded, size is less than total size [ %d ])\n",
1118 case MATCHCRITERIA_NEWSGROUPS
:
1119 return matcherprop_string_match(prop
, info
->newsgroups
, context_str
[CONTEXT_NEWSGROUPS
]);
1120 case MATCHCRITERIA_NOT_NEWSGROUPS
:
1121 return !matcherprop_string_match(prop
, info
->newsgroups
, context_str
[CONTEXT_NEWSGROUPS
]);
1122 case MATCHCRITERIA_MESSAGEID
:
1123 return matcherprop_string_match(prop
, info
->msgid
, context_str
[CONTEXT_MESSAGEID
]);
1124 case MATCHCRITERIA_NOT_MESSAGEID
:
1125 return !matcherprop_string_match(prop
, info
->msgid
, context_str
[CONTEXT_MESSAGEID
]);
1126 case MATCHCRITERIA_INREPLYTO
:
1127 return matcherprop_string_match(prop
, info
->inreplyto
, context_str
[CONTEXT_IN_REPLY_TO
]);
1128 case MATCHCRITERIA_NOT_INREPLYTO
:
1129 return !matcherprop_string_match(prop
, info
->inreplyto
, context_str
[CONTEXT_IN_REPLY_TO
]);
1130 case MATCHCRITERIA_REFERENCES
:
1131 return matcherprop_list_match(prop
, info
->references
, context_str
[CONTEXT_REFERENCES
]);
1132 case MATCHCRITERIA_NOT_REFERENCES
:
1133 return !matcherprop_list_match(prop
, info
->references
, context_str
[CONTEXT_REFERENCES
]);
1134 case MATCHCRITERIA_TEST
:
1135 return matcherprop_match_test(prop
, info
);
1136 case MATCHCRITERIA_NOT_TEST
:
1137 return !matcherprop_match_test(prop
, info
);
1143 /* ********************* MatcherList *************************** */
1146 *\brief Create a new list of matchers
1148 *\param matchers List of matcher structures
1149 *\param bool_and Operator
1151 *\return MatcherList * New list
1153 MatcherList
*matcherlist_new(GSList
*matchers
, gboolean bool_and
)
1157 cond
= g_new0(MatcherList
, 1);
1159 cond
->matchers
= matchers
;
1160 cond
->bool_and
= bool_and
;
1167 *\brief Builds a single regular expresion from an array of srings.
1169 *\param strings The lines containing the different sub-regexp.
1171 *\return The newly allocated regexp string.
1173 static gchar
*build_complete_regexp(gchar
**strings
)
1177 while (strings
&& strings
[i
] && *strings
[i
]) {
1178 int old_len
= expr
? strlen(expr
):0;
1180 gchar
*tmpstr
= NULL
;
1182 if (g_utf8_validate(strings
[i
], -1, NULL
))
1183 tmpstr
= g_strdup(strings
[i
]);
1185 tmpstr
= conv_codeset_strdup(strings
[i
],
1186 conv_get_locale_charset_str_no_utf8(),
1189 if (strstr(tmpstr
, "\n"))
1190 *(strstr(tmpstr
, "\n")) = '\0';
1192 new_len
= strlen(tmpstr
);
1194 expr
= g_realloc(expr
,
1195 expr
? (old_len
+ strlen("|()") + new_len
+ 1)
1196 : (strlen("()") + new_len
+ 1));
1199 strcpy(expr
+ old_len
, "|(");
1200 strcpy(expr
+ old_len
+ 2, tmpstr
);
1201 strcpy(expr
+ old_len
+ 2 + new_len
, ")");
1203 strcpy(expr
+old_len
, "(");
1204 strcpy(expr
+old_len
+ 1, tmpstr
);
1205 strcpy(expr
+old_len
+ 1 + new_len
, ")");
1215 *\brief Create a new list of matchers from a multi-line string
1217 *\param lines String with "\n"-separated expressions
1218 *\param bool_and Operator
1219 *\param case_sensitive If the matching is case sensitive or not
1221 *\return MatcherList * New matcher list
1223 MatcherList
*matcherlist_new_from_lines(gchar
*lines
, gboolean bool_and
,
1224 gboolean case_sensitive
)
1226 MatcherProp
*m
= NULL
;
1227 GSList
*matchers
= NULL
;
1228 gchar
**strings
= g_strsplit(lines
, "\n", -1);
1232 expr
= build_complete_regexp(strings
);
1233 debug_print("building matcherprop for expr '%s'\n", expr
?expr
:"NULL");
1235 m
= matcherprop_new(MATCHCRITERIA_SUBJECT
, NULL
,
1236 case_sensitive
? MATCHTYPE_REGEXP
: MATCHTYPE_REGEXPCASE
,
1239 /* print error message */
1240 debug_print("failed to allocate memory for matcherprop\n");
1242 matchers
= g_slist_append(matchers
, m
);
1248 while (strings
&& strings
[i
] && *strings
[i
]) {
1249 m
= matcherprop_new(MATCHCRITERIA_SUBJECT
, NULL
,
1250 case_sensitive
? MATCHTYPE_MATCH
: MATCHTYPE_MATCHCASE
,
1253 /* print error message */
1254 debug_print("failed to allocate memory for matcherprop\n");
1256 matchers
= g_slist_append(matchers
, m
);
1261 g_strfreev(strings
);
1263 return matcherlist_new(matchers
, bool_and
);
1267 *\brief Frees a list of matchers
1269 *\param cond List of matchers
1271 void matcherlist_free(MatcherList
*cond
)
1275 cm_return_if_fail(cond
);
1276 for (l
= cond
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1277 matcherprop_free((MatcherProp
*) l
->data
);
1279 g_slist_free(cond
->matchers
);
1284 *\brief Skip all headers in a message file
1286 *\param fp Message file
1288 static void matcherlist_skip_headers(FILE *fp
)
1290 gchar buf
[BUFFSIZE
];
1292 while (procheader_get_one_field(buf
, sizeof(buf
), fp
, NULL
) != -1)
1297 *\brief Check if a header matches a matcher condition
1299 *\param matcher Matcher structure to check header for
1300 *\param buf Header name
1302 *\return boolean TRUE if matching header
1304 static gboolean
matcherprop_match_one_header(MatcherProp
*matcher
,
1307 gboolean result
= FALSE
;
1308 Header
*header
= NULL
;
1310 switch (matcher
->criteria
) {
1311 case MATCHCRITERIA_HEADER
:
1312 case MATCHCRITERIA_NOT_HEADER
:
1313 header
= procheader_parse_header(buf
);
1316 if (procheader_headername_equal(header
->name
,
1318 if (matcher
->criteria
== MATCHCRITERIA_HEADER
)
1319 result
= matcherprop_string_match(matcher
, header
->body
, context_str
[CONTEXT_HEADER
]);
1321 result
= !matcherprop_string_match(matcher
, header
->body
, context_str
[CONTEXT_HEADER
]);
1322 procheader_header_free(header
);
1326 procheader_header_free(header
);
1329 case MATCHCRITERIA_HEADERS_PART
:
1330 case MATCHCRITERIA_HEADERS_CONT
:
1331 case MATCHCRITERIA_MESSAGE
:
1332 header
= procheader_parse_header(buf
);
1335 result
= matcherprop_header_line_match(matcher
,
1336 header
->name
, header
->body
,
1337 (matcher
->criteria
== MATCHCRITERIA_HEADERS_PART
),
1338 context_str
[CONTEXT_HEADER_LINE
]);
1339 procheader_header_free(header
);
1341 case MATCHCRITERIA_NOT_HEADERS_CONT
:
1342 case MATCHCRITERIA_NOT_HEADERS_PART
:
1343 case MATCHCRITERIA_NOT_MESSAGE
:
1344 header
= procheader_parse_header(buf
);
1347 result
= !matcherprop_header_line_match(matcher
,
1348 header
->name
, header
->body
,
1349 (matcher
->criteria
== MATCHCRITERIA_NOT_HEADERS_PART
),
1350 context_str
[CONTEXT_HEADER_LINE
]);
1351 procheader_header_free(header
);
1353 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
:
1354 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
:
1356 GSList
*address_list
= NULL
;
1357 gint match
= MATCH_ONE
;
1358 gboolean found
= FALSE
;
1360 /* how many address headers are we trying to match? */
1361 if (strcasecmp(matcher
->header
, "Any") == 0)
1363 else if (strcasecmp(matcher
->header
, "All") == 0)
1366 if (match
== MATCH_ONE
) {
1367 /* matching one address header exactly, is that the right one? */
1368 header
= procheader_parse_header(buf
);
1370 !procheader_headername_equal(header
->name
, matcher
->header
))
1372 address_list
= address_list_append(address_list
, header
->body
);
1373 if (address_list
== NULL
)
1377 header
= procheader_parse_header(buf
);
1380 /* address header is one of the headers we have to match when checking
1381 for any address header or all address headers? */
1382 if (procheader_headername_equal(header
->name
, "From") ||
1383 procheader_headername_equal(header
->name
, "To") ||
1384 procheader_headername_equal(header
->name
, "Cc") ||
1385 procheader_headername_equal(header
->name
, "Reply-To") ||
1386 procheader_headername_equal(header
->name
, "Sender"))
1387 address_list
= address_list_append(address_list
, header
->body
);
1388 if (address_list
== NULL
)
1392 found
= match_with_addresses_in_addressbook
1393 (matcher
, address_list
, matcher
->criteria
,
1394 matcher
->expr
, match
);
1395 g_slist_free(address_list
);
1397 if (matcher
->criteria
== MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
)
1408 *\brief Check if the matcher structure wants headers to
1411 *\param matcher Matcher structure
1413 *\return gboolean TRUE if the matcher structure describes
1414 * a header match condition
1416 static gboolean
matcherprop_criteria_headers(const MatcherProp
*matcher
)
1418 switch (matcher
->criteria
) {
1419 case MATCHCRITERIA_HEADER
:
1420 case MATCHCRITERIA_NOT_HEADER
:
1421 case MATCHCRITERIA_HEADERS_PART
:
1422 case MATCHCRITERIA_HEADERS_CONT
:
1423 case MATCHCRITERIA_NOT_HEADERS_PART
:
1424 case MATCHCRITERIA_NOT_HEADERS_CONT
:
1425 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
:
1426 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
:
1434 *\brief Check if the matcher structure wants the message
1435 * to be matched (just perform an action on any
1438 *\param matcher Matcher structure
1440 *\return gboolean TRUE if matcher condition should match
1443 static gboolean
matcherprop_criteria_message(MatcherProp
*matcher
)
1445 switch (matcher
->criteria
) {
1446 case MATCHCRITERIA_MESSAGE
:
1447 case MATCHCRITERIA_NOT_MESSAGE
:
1455 *\brief Check if a list of conditions matches one header in
1458 *\param matchers List of conditions
1459 *\param fp Message file
1461 *\return gboolean TRUE if one of the headers is matched by
1462 * the list of conditions.
1464 static gboolean
matcherlist_match_headers(MatcherList
*matchers
, FILE *fp
)
1467 gchar buf
[BUFFSIZE
];
1469 while (procheader_get_one_field(buf
, sizeof(buf
), fp
, NULL
) != -1) {
1470 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1471 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1472 gint match
= MATCH_ANY
;
1477 /* determine the match range (all, any are our concern here) */
1478 if (matcher
->criteria
== MATCHCRITERIA_NOT_HEADERS_PART
||
1479 matcher
->criteria
== MATCHCRITERIA_NOT_HEADERS_CONT
||
1480 matcher
->criteria
== MATCHCRITERIA_NOT_MESSAGE
) {
1483 } else if (matcher
->criteria
== MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
||
1484 matcher
->criteria
== MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
) {
1485 Header
*header
= NULL
;
1487 /* address header is one of the headers we have to match when checking
1488 for any address header or all address headers? */
1489 header
= procheader_parse_header(buf
);
1491 (procheader_headername_equal(header
->name
, "From") ||
1492 procheader_headername_equal(header
->name
, "To") ||
1493 procheader_headername_equal(header
->name
, "Cc") ||
1494 procheader_headername_equal(header
->name
, "Reply-To") ||
1495 procheader_headername_equal(header
->name
, "Sender"))) {
1497 if (strcasecmp(matcher
->header
, "Any") == 0)
1499 else if (strcasecmp(matcher
->header
, "All") == 0)
1504 /* further call to matcherprop_match_one_header() can't match
1505 and it irrelevant, so: don't alter the match result */
1510 /* ZERO line must NOT match for the rule to match.
1512 if (match
== MATCH_ALL
) {
1513 if (matcherprop_match_one_header(matcher
, buf
)) {
1514 matcher
->result
= TRUE
;
1516 matcher
->result
= FALSE
;
1517 matcher
->done
= TRUE
;
1519 /* else, just one line matching is enough for the rule to match
1521 } else if (matcherprop_criteria_headers(matcher
) ||
1522 matcherprop_criteria_message(matcher
)) {
1523 if (matcherprop_match_one_header(matcher
, buf
)) {
1524 matcher
->result
= TRUE
;
1525 matcher
->done
= TRUE
;
1529 /* if the rule matched and the matchers are OR, no need to
1530 * check the others */
1531 if (matcher
->result
&& matcher
->done
) {
1532 if (!matchers
->bool_and
)
1542 *\brief Check if a matcher wants to check the message body
1544 *\param matcher Matcher structure
1546 *\return gboolean TRUE if body must be matched.
1548 static gboolean
matcherprop_criteria_body(const MatcherProp
*matcher
)
1550 switch (matcher
->criteria
) {
1551 case MATCHCRITERIA_BODY_PART
:
1552 case MATCHCRITERIA_NOT_BODY_PART
:
1559 static gboolean
matcherlist_match_binary_content(MatcherList
*matchers
, MimeInfo
*partinfo
)
1562 gchar buf
[BUFFSIZE
];
1565 if (!partinfo
|| partinfo
->type
== MIMETYPE_TEXT
)
1568 outfp
= procmime_get_binary_content(partinfo
);
1573 while (fgets(buf
, sizeof(buf
), outfp
) != NULL
) {
1576 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1577 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1582 /* Don't scan non-text parts when looking in body, only
1583 * when looking in whole message
1585 if (matcher
->criteria
== MATCHCRITERIA_NOT_BODY_PART
||
1586 matcher
->criteria
== MATCHCRITERIA_BODY_PART
)
1589 /* if the criteria is ~body_part or ~message, ZERO lines
1590 * must match for the rule to match.
1592 if (matcher
->criteria
== MATCHCRITERIA_NOT_BODY_PART
||
1593 matcher
->criteria
== MATCHCRITERIA_NOT_MESSAGE
) {
1594 if (matcherprop_string_match(matcher
, buf
,
1595 context_str
[CONTEXT_BODY_LINE
])) {
1596 matcher
->result
= FALSE
;
1597 matcher
->done
= TRUE
;
1599 matcher
->result
= TRUE
;
1600 /* else, just one line has to match */
1601 } else if (matcherprop_criteria_body(matcher
) ||
1602 matcherprop_criteria_message(matcher
)) {
1603 if (matcherprop_string_match(matcher
, buf
,
1604 context_str
[CONTEXT_BODY_LINE
])) {
1605 matcher
->result
= TRUE
;
1606 matcher
->done
= TRUE
;
1610 /* if the matchers are OR'ed and the rule matched,
1611 * no need to check the others. */
1612 if (matcher
->result
&& matcher
->done
) {
1613 if (!matchers
->bool_and
) {
1625 static gboolean
match_content_cb(const gchar
*buf
, gpointer data
)
1627 MatcherList
*matchers
= (MatcherList
*)data
;
1628 gboolean all_done
= TRUE
;
1631 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1632 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1637 /* if the criteria is ~body_part or ~message, ZERO lines
1638 * must match for the rule to match.
1640 if (matcher
->criteria
== MATCHCRITERIA_NOT_BODY_PART
||
1641 matcher
->criteria
== MATCHCRITERIA_NOT_MESSAGE
) {
1642 if (matcherprop_string_match(matcher
, buf
,
1643 context_str
[CONTEXT_BODY_LINE
])) {
1644 matcher
->result
= FALSE
;
1645 matcher
->done
= TRUE
;
1647 matcher
->result
= TRUE
;
1648 /* else, just one line has to match */
1649 } else if (matcherprop_criteria_body(matcher
) ||
1650 matcherprop_criteria_message(matcher
)) {
1651 if (matcherprop_string_match(matcher
, buf
,
1652 context_str
[CONTEXT_BODY_LINE
])) {
1653 matcher
->result
= TRUE
;
1654 matcher
->done
= TRUE
;
1658 /* if the matchers are OR'ed and the rule matched,
1659 * no need to check the others. */
1660 if (matcher
->result
&& matcher
->done
) {
1661 if (!matchers
->bool_and
) {
1672 static gboolean
matcherlist_match_text_content(MatcherList
*matchers
, MimeInfo
*partinfo
)
1674 if (partinfo
->type
!= MIMETYPE_TEXT
)
1677 return procmime_scan_text_content(partinfo
, match_content_cb
, matchers
);
1681 *\brief Check if a line in a message file's body matches
1684 *\param matchers List of conditions
1685 *\param fp Message file
1687 *\return gboolean TRUE if succesful match
1689 static gboolean
matcherlist_match_body(MatcherList
*matchers
, gboolean body_only
, MsgInfo
*info
)
1691 MimeInfo
*mimeinfo
= NULL
;
1692 MimeInfo
*partinfo
= NULL
;
1693 gboolean first_text_found
= FALSE
;
1695 cm_return_val_if_fail(info
!= NULL
, FALSE
);
1697 mimeinfo
= procmime_scan_message(info
);
1700 partinfo
= procmime_mimeinfo_next(mimeinfo
);
1702 for (; partinfo
!= NULL
; partinfo
= procmime_mimeinfo_next(partinfo
)) {
1704 if (partinfo
->type
!= MIMETYPE_TEXT
&& body_only
)
1707 if (partinfo
->type
== MIMETYPE_TEXT
) {
1708 first_text_found
= TRUE
;
1709 if (matcherlist_match_text_content(matchers
, partinfo
)) {
1710 procmime_mimeinfo_free_all(&mimeinfo
);
1713 } else if (matcherlist_match_binary_content(matchers
, partinfo
)) {
1714 procmime_mimeinfo_free_all(&mimeinfo
);
1718 if (body_only
&& first_text_found
)
1721 procmime_mimeinfo_free_all(&mimeinfo
);
1727 *\brief Check if a message file matches criteria
1729 *\param matchers Criteria
1730 *\param info Message info
1731 *\param result Default result
1733 *\return gboolean TRUE if matched
1735 static gboolean
matcherlist_match_file(MatcherList
*matchers
, MsgInfo
*info
,
1738 gboolean read_headers
;
1745 /* file need to be read ? */
1747 read_headers
= FALSE
;
1750 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1751 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1753 if (matcherprop_criteria_headers(matcher
))
1754 read_headers
= TRUE
;
1755 if (matcherprop_criteria_body(matcher
))
1757 if (matcherprop_criteria_message(matcher
)) {
1758 read_headers
= TRUE
;
1762 matcher
->result
= FALSE
;
1763 matcher
->done
= FALSE
;
1766 if (!read_headers
&& !read_body
)
1769 file
= procmsg_get_message_file_full(info
, read_headers
, read_body
);
1773 if ((fp
= g_fopen(file
, "rb")) == NULL
) {
1774 FILE_OP_ERROR(file
, "fopen");
1779 /* read the headers */
1782 if (matcherlist_match_headers(matchers
, fp
))
1785 matcherlist_skip_headers(fp
);
1790 matcherlist_match_body(matchers
, body_only
, info
);
1793 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1794 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1796 if (matcherprop_criteria_headers(matcher
) ||
1797 matcherprop_criteria_body(matcher
) ||
1798 matcherprop_criteria_message(matcher
)) {
1799 if (matcher
->result
) {
1800 if (!matchers
->bool_and
) {
1806 if (matchers
->bool_and
) {
1822 *\brief Test list of conditions on a message.
1824 *\param matchers List of conditions
1825 *\param info Message info
1827 *\return gboolean TRUE if matched
1829 gboolean
matcherlist_match(MatcherList
*matchers
, MsgInfo
*info
)
1837 if (matchers
->bool_and
)
1842 /* test the cached elements */
1844 for (l
= matchers
->matchers
; l
!= NULL
;l
= g_slist_next(l
)) {
1845 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1847 if (debug_filtering_session
) {
1848 gchar
*buf
= matcherprop_to_string(matcher
);
1849 log_print(LOG_DEBUG_FILTERING
, _("checking if message matches [ %s ]\n"), buf
);
1853 switch(matcher
->criteria
) {
1854 case MATCHCRITERIA_ALL
:
1855 case MATCHCRITERIA_UNREAD
:
1856 case MATCHCRITERIA_NOT_UNREAD
:
1857 case MATCHCRITERIA_NEW
:
1858 case MATCHCRITERIA_NOT_NEW
:
1859 case MATCHCRITERIA_MARKED
:
1860 case MATCHCRITERIA_NOT_MARKED
:
1861 case MATCHCRITERIA_DELETED
:
1862 case MATCHCRITERIA_NOT_DELETED
:
1863 case MATCHCRITERIA_REPLIED
:
1864 case MATCHCRITERIA_NOT_REPLIED
:
1865 case MATCHCRITERIA_FORWARDED
:
1866 case MATCHCRITERIA_NOT_FORWARDED
:
1867 case MATCHCRITERIA_LOCKED
:
1868 case MATCHCRITERIA_NOT_LOCKED
:
1869 case MATCHCRITERIA_SPAM
:
1870 case MATCHCRITERIA_NOT_SPAM
:
1871 case MATCHCRITERIA_HAS_ATTACHMENT
:
1872 case MATCHCRITERIA_HAS_NO_ATTACHMENT
:
1873 case MATCHCRITERIA_SIGNED
:
1874 case MATCHCRITERIA_NOT_SIGNED
:
1875 case MATCHCRITERIA_COLORLABEL
:
1876 case MATCHCRITERIA_NOT_COLORLABEL
:
1877 case MATCHCRITERIA_IGNORE_THREAD
:
1878 case MATCHCRITERIA_NOT_IGNORE_THREAD
:
1879 case MATCHCRITERIA_WATCH_THREAD
:
1880 case MATCHCRITERIA_NOT_WATCH_THREAD
:
1881 case MATCHCRITERIA_SUBJECT
:
1882 case MATCHCRITERIA_NOT_SUBJECT
:
1883 case MATCHCRITERIA_FROM
:
1884 case MATCHCRITERIA_NOT_FROM
:
1885 case MATCHCRITERIA_TO
:
1886 case MATCHCRITERIA_NOT_TO
:
1887 case MATCHCRITERIA_CC
:
1888 case MATCHCRITERIA_NOT_CC
:
1889 case MATCHCRITERIA_TO_OR_CC
:
1890 case MATCHCRITERIA_NOT_TO_AND_NOT_CC
:
1891 case MATCHCRITERIA_TAG
:
1892 case MATCHCRITERIA_NOT_TAG
:
1893 case MATCHCRITERIA_TAGGED
:
1894 case MATCHCRITERIA_NOT_TAGGED
:
1895 case MATCHCRITERIA_AGE_GREATER
:
1896 case MATCHCRITERIA_AGE_LOWER
:
1897 case MATCHCRITERIA_AGE_GREATER_HOURS
:
1898 case MATCHCRITERIA_AGE_LOWER_HOURS
:
1899 case MATCHCRITERIA_NEWSGROUPS
:
1900 case MATCHCRITERIA_NOT_NEWSGROUPS
:
1901 case MATCHCRITERIA_MESSAGEID
:
1902 case MATCHCRITERIA_NOT_MESSAGEID
:
1903 case MATCHCRITERIA_INREPLYTO
:
1904 case MATCHCRITERIA_NOT_INREPLYTO
:
1905 case MATCHCRITERIA_REFERENCES
:
1906 case MATCHCRITERIA_NOT_REFERENCES
:
1907 case MATCHCRITERIA_SCORE_GREATER
:
1908 case MATCHCRITERIA_SCORE_LOWER
:
1909 case MATCHCRITERIA_SCORE_EQUAL
:
1910 case MATCHCRITERIA_SIZE_GREATER
:
1911 case MATCHCRITERIA_SIZE_SMALLER
:
1912 case MATCHCRITERIA_SIZE_EQUAL
:
1913 case MATCHCRITERIA_TEST
:
1914 case MATCHCRITERIA_NOT_TEST
:
1915 case MATCHCRITERIA_PARTIAL
:
1916 case MATCHCRITERIA_NOT_PARTIAL
:
1917 if (matcherprop_match(matcher
, info
)) {
1918 if (!matchers
->bool_and
) {
1919 if (debug_filtering_session
)
1920 log_status_ok(LOG_DEBUG_FILTERING
, _("message matches\n"));
1925 if (matchers
->bool_and
) {
1926 if (debug_filtering_session
)
1927 log_status_nok(LOG_DEBUG_FILTERING
, _("message does not match\n"));
1934 /* test the condition on the file */
1936 if (matcherlist_match_file(matchers
, info
, result
)) {
1937 if (!matchers
->bool_and
) {
1938 if (debug_filtering_session
)
1939 log_status_ok(LOG_DEBUG_FILTERING
, _("message matches\n"));
1943 if (matchers
->bool_and
) {
1944 if (debug_filtering_session
)
1945 log_status_nok(LOG_DEBUG_FILTERING
, _("message does not match\n"));
1950 if (debug_filtering_session
) {
1952 log_status_ok(LOG_DEBUG_FILTERING
, _("message matches\n"));
1954 log_status_nok(LOG_DEBUG_FILTERING
, _("message does not match\n"));
1960 static gint
quote_filter_str(gchar
* result
, guint size
,
1970 for(p
= path
; * p
!= '\0' ; p
++) {
1972 if ((* p
!= '\"') && (* p
!= '\\')) {
1973 if (remaining
> 0) {
1979 result
[size
- 1] = '\0';
1984 if (remaining
>= 2) {
1992 result
[size
- 1] = '\0';
1997 if (remaining
> 0) {
2001 result
[size
- 1] = '\0';
2009 gchar
* matcher_quote_str(const gchar
* src
)
2014 len
= strlen(src
) * 2 + 1;
2015 res
= g_malloc(len
);
2016 quote_filter_str(res
, len
, src
);
2022 *\brief Convert a matcher structure to a string
2024 *\param matcher Matcher structure
2026 *\return gchar * Newly allocated string
2028 gchar
*matcherprop_to_string(MatcherProp
*matcher
)
2030 gchar
*matcher_str
= NULL
;
2031 const gchar
*criteria_str
;
2032 const gchar
*matchtype_str
;
2034 gchar
* quoted_expr
;
2035 gchar
* quoted_header
;
2037 criteria_str
= NULL
;
2038 for (i
= 0; i
< (int) (sizeof(matchparser_tab
) / sizeof(MatchParser
)); i
++) {
2039 if (matchparser_tab
[i
].id
== matcher
->criteria
)
2040 criteria_str
= matchparser_tab
[i
].str
;
2042 if (criteria_str
== NULL
)
2045 switch (matcher
->criteria
) {
2046 case MATCHCRITERIA_AGE_GREATER
:
2047 case MATCHCRITERIA_AGE_LOWER
:
2048 case MATCHCRITERIA_AGE_GREATER_HOURS
:
2049 case MATCHCRITERIA_AGE_LOWER_HOURS
:
2050 case MATCHCRITERIA_SCORE_GREATER
:
2051 case MATCHCRITERIA_SCORE_LOWER
:
2052 case MATCHCRITERIA_SCORE_EQUAL
:
2053 case MATCHCRITERIA_SIZE_GREATER
:
2054 case MATCHCRITERIA_SIZE_SMALLER
:
2055 case MATCHCRITERIA_SIZE_EQUAL
:
2056 case MATCHCRITERIA_COLORLABEL
:
2057 case MATCHCRITERIA_NOT_COLORLABEL
:
2058 return g_strdup_printf("%s %i", criteria_str
, matcher
->value
);
2059 case MATCHCRITERIA_ALL
:
2060 case MATCHCRITERIA_UNREAD
:
2061 case MATCHCRITERIA_NOT_UNREAD
:
2062 case MATCHCRITERIA_NEW
:
2063 case MATCHCRITERIA_NOT_NEW
:
2064 case MATCHCRITERIA_MARKED
:
2065 case MATCHCRITERIA_NOT_MARKED
:
2066 case MATCHCRITERIA_DELETED
:
2067 case MATCHCRITERIA_NOT_DELETED
:
2068 case MATCHCRITERIA_REPLIED
:
2069 case MATCHCRITERIA_NOT_REPLIED
:
2070 case MATCHCRITERIA_FORWARDED
:
2071 case MATCHCRITERIA_NOT_FORWARDED
:
2072 case MATCHCRITERIA_LOCKED
:
2073 case MATCHCRITERIA_NOT_LOCKED
:
2074 case MATCHCRITERIA_SPAM
:
2075 case MATCHCRITERIA_NOT_SPAM
:
2076 case MATCHCRITERIA_HAS_ATTACHMENT
:
2077 case MATCHCRITERIA_HAS_NO_ATTACHMENT
:
2078 case MATCHCRITERIA_SIGNED
:
2079 case MATCHCRITERIA_NOT_SIGNED
:
2080 case MATCHCRITERIA_PARTIAL
:
2081 case MATCHCRITERIA_NOT_PARTIAL
:
2082 case MATCHCRITERIA_IGNORE_THREAD
:
2083 case MATCHCRITERIA_NOT_IGNORE_THREAD
:
2084 case MATCHCRITERIA_WATCH_THREAD
:
2085 case MATCHCRITERIA_NOT_WATCH_THREAD
:
2086 case MATCHCRITERIA_TAGGED
:
2087 case MATCHCRITERIA_NOT_TAGGED
:
2088 return g_strdup(criteria_str
);
2089 case MATCHCRITERIA_TEST
:
2090 case MATCHCRITERIA_NOT_TEST
:
2091 quoted_expr
= matcher_quote_str(matcher
->expr
);
2092 matcher_str
= g_strdup_printf("%s \"%s\"",
2093 criteria_str
, quoted_expr
);
2094 g_free(quoted_expr
);
2096 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
:
2097 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
:
2098 quoted_header
= matcher_quote_str(matcher
->header
);
2099 quoted_expr
= matcher_quote_str(matcher
->expr
);
2100 matcher_str
= g_strdup_printf("%s \"%s\" in \"%s\"",
2101 criteria_str
, quoted_header
, quoted_expr
);
2102 g_free(quoted_header
);
2103 g_free(quoted_expr
);
2107 matchtype_str
= NULL
;
2108 for (i
= 0; i
< sizeof matchparser_tab
/ sizeof matchparser_tab
[0]; i
++) {
2109 if (matchparser_tab
[i
].id
== matcher
->matchtype
)
2110 matchtype_str
= matchparser_tab
[i
].str
;
2113 if (matchtype_str
== NULL
)
2116 switch (matcher
->matchtype
) {
2117 case MATCHTYPE_MATCH
:
2118 case MATCHTYPE_MATCHCASE
:
2119 case MATCHTYPE_REGEXP
:
2120 case MATCHTYPE_REGEXPCASE
:
2121 quoted_expr
= matcher_quote_str(matcher
->expr
);
2122 if (matcher
->header
) {
2123 quoted_header
= matcher_quote_str(matcher
->header
);
2124 matcher_str
= g_strdup_printf
2125 ("%s \"%s\" %s \"%s\"",
2126 criteria_str
, quoted_header
,
2127 matchtype_str
, quoted_expr
);
2128 g_free(quoted_header
);
2131 matcher_str
= g_strdup_printf
2132 ("%s %s \"%s\"", criteria_str
,
2133 matchtype_str
, quoted_expr
);
2134 g_free(quoted_expr
);
2142 *\brief Convert a list of conditions to a string
2144 *\param matchers List of conditions
2146 *\return gchar * Newly allocated string
2148 gchar
*matcherlist_to_string(const MatcherList
*matchers
)
2154 gchar
*result
= NULL
;
2156 count
= g_slist_length(matchers
->matchers
);
2157 vstr
= g_new(gchar
*, count
+ 1);
2159 for (l
= matchers
->matchers
, cur_str
= vstr
; l
!= NULL
;
2160 l
= g_slist_next(l
), cur_str
++) {
2161 *cur_str
= matcherprop_to_string((MatcherProp
*) l
->data
);
2162 if (*cur_str
== NULL
)
2167 if (matchers
->bool_and
)
2168 result
= g_strjoinv(" & ", vstr
);
2170 result
= g_strjoinv(" | ", vstr
);
2172 for (cur_str
= vstr
; *cur_str
!= NULL
; cur_str
++)
2180 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
2181 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
2183 static void add_str_default(gchar
** dest
,
2184 const gchar
* s
, const gchar
* d
)
2186 gchar quoted_str
[4096];
2194 quote_cmd_argument(quoted_str
, sizeof(quoted_str
), str
);
2195 strcpy(* dest
, quoted_str
);
2197 (* dest
) += strlen(* dest
);
2200 /* matching_build_command() - preferably cmd should be unescaped */
2202 *\brief Build the command-line to execute
2204 *\param cmd String with command-line specifiers
2205 *\param info Message info to use for command
2207 *\return gchar * Newly allocated string
2209 gchar
*matching_build_command(const gchar
*cmd
, MsgInfo
*info
)
2211 const gchar
*s
= cmd
;
2212 gchar
*filename
= NULL
;
2213 gchar
*processed_cmd
;
2217 const gchar
*const no_subject
= _("(none)") ;
2218 const gchar
*const no_from
= _("(none)") ;
2219 const gchar
*const no_to
= _("(none)") ;
2220 const gchar
*const no_cc
= _("(none)") ;
2221 const gchar
*const no_date
= _("(none)") ;
2222 const gchar
*const no_msgid
= _("(none)") ;
2223 const gchar
*const no_newsgroups
= _("(none)") ;
2224 const gchar
*const no_references
= _("(none)") ;
2226 size
= STRLEN_ZERO(cmd
) + 1;
2227 while (*s
!= '\0') {
2234 case 's': /* subject */
2235 size
+= STRLEN_DEFAULT(info
->subject
, no_subject
) - 2;
2237 case 'f': /* from */
2238 size
+= STRLEN_DEFAULT(info
->from
, no_from
) - 2;
2241 size
+= STRLEN_DEFAULT(info
->to
, no_to
) - 2;
2244 size
+= STRLEN_DEFAULT(info
->cc
, no_cc
) - 2;
2246 case 'd': /* date */
2247 size
+= STRLEN_DEFAULT(info
->date
, no_date
) - 2;
2249 case 'i': /* message-id */
2250 size
+= STRLEN_DEFAULT(info
->msgid
, no_msgid
) - 2;
2252 case 'n': /* newsgroups */
2253 size
+= STRLEN_DEFAULT(info
->newsgroups
, no_newsgroups
) - 2;
2255 case 'r': /* references */
2256 /* FIXME: using the inreplyto header for reference */
2257 size
+= STRLEN_DEFAULT(info
->inreplyto
, no_references
) - 2;
2259 case 'F': /* file */
2260 if (filename
== NULL
)
2261 filename
= folder_item_fetch_msg(info
->folder
, info
->msgnum
);
2263 if (filename
== NULL
) {
2264 g_warning("filename is not set");
2268 size
+= strlen(filename
) - 2;
2277 /* as the string can be quoted, we double the result */
2280 processed_cmd
= g_new0(gchar
, size
);
2284 while (*s
!= '\0') {
2292 case 's': /* subject */
2293 add_str_default(&p
, info
->subject
,
2296 case 'f': /* from */
2297 add_str_default(&p
, info
->from
,
2301 add_str_default(&p
, info
->to
,
2305 add_str_default(&p
, info
->cc
,
2308 case 'd': /* date */
2309 add_str_default(&p
, info
->date
,
2312 case 'i': /* message-id */
2313 add_str_default(&p
, info
->msgid
,
2316 case 'n': /* newsgroups */
2317 add_str_default(&p
, info
->newsgroups
,
2320 case 'r': /* references */
2321 /* FIXME: using the inreplyto header for references */
2322 add_str_default(&p
, info
->inreplyto
, no_references
);
2324 case 'F': /* file */
2325 if (filename
!= NULL
)
2326 add_str_default(&p
, filename
, NULL
);
2345 return processed_cmd
;
2347 #undef STRLEN_DEFAULT
2350 /* ************************************************************ */
2354 *\brief Write filtering list to file
2357 *\param prefs_filtering List of filtering conditions
2359 static int prefs_filtering_write(FILE *fp
, GSList
*prefs_filtering
)
2363 for (cur
= prefs_filtering
; cur
!= NULL
; cur
= cur
->next
) {
2364 gchar
*filtering_str
= NULL
;
2365 gchar
*tmp_name
= NULL
;
2366 FilteringProp
*prop
= NULL
;
2368 if (NULL
== (prop
= (FilteringProp
*) cur
->data
))
2371 if (NULL
== (filtering_str
= filteringprop_to_string(prop
)))
2374 if (prop
->enabled
) {
2375 if (fputs("enabled ", fp
) == EOF
) {
2376 FILE_OP_ERROR("filtering config", "fputs");
2380 if (fputs("disabled ", fp
) == EOF
) {
2381 FILE_OP_ERROR("filtering config", "fputs");
2386 if (fputs("rulename \"", fp
) == EOF
) {
2387 FILE_OP_ERROR("filtering config", "fputs");
2388 g_free(filtering_str
);
2391 tmp_name
= prop
->name
;
2392 while (tmp_name
&& *tmp_name
!= '\0') {
2393 if (*tmp_name
!= '"') {
2394 if (fputc(*tmp_name
, fp
) == EOF
) {
2395 FILE_OP_ERROR("filtering config", "fputs || fputc");
2396 g_free(filtering_str
);
2399 } else if (*tmp_name
== '"') {
2400 if (fputc('\\', fp
) == EOF
||
2401 fputc('"', fp
) == EOF
) {
2402 FILE_OP_ERROR("filtering config", "fputs || fputc");
2403 g_free(filtering_str
);
2409 if (fputs("\" ", fp
) == EOF
) {
2410 FILE_OP_ERROR("filtering config", "fputs");
2411 g_free(filtering_str
);
2415 if (prop
->account_id
!= 0) {
2418 tmp
= g_strdup_printf("account %d ", prop
->account_id
);
2419 if (fputs(tmp
, fp
) == EOF
) {
2420 FILE_OP_ERROR("filtering config", "fputs");
2427 if(fputs(filtering_str
, fp
) == EOF
||
2428 fputc('\n', fp
) == EOF
) {
2429 FILE_OP_ERROR("filtering config", "fputs || fputc");
2430 g_free(filtering_str
);
2433 g_free(filtering_str
);
2439 typedef struct _NodeLoopData
{
2445 *\brief Write matchers from a folder item
2447 *\param node Node with folder info
2448 *\param data File pointer
2450 *\return gboolean FALSE
2452 static gboolean
prefs_matcher_write_func(GNode
*node
, gpointer d
)
2455 NodeLoopData
*data
= (NodeLoopData
*)d
;
2457 GSList
*prefs_filtering
;
2460 /* prevent warning */
2461 if (item
->path
== NULL
)
2463 id
= folder_item_get_identifier(item
);
2466 prefs_filtering
= item
->prefs
->processing
;
2468 if (prefs_filtering
!= NULL
) {
2469 if (fprintf(data
->fp
, "[%s]\n", id
) < 0) {
2473 if (prefs_filtering_write(data
->fp
, prefs_filtering
) < 0) {
2477 if (fputc('\n', data
->fp
) == EOF
) {
2489 *\brief Save matchers from folder items
2493 static int prefs_matcher_save(FILE *fp
)
2501 for (cur
= folder_get_list() ; cur
!= NULL
; cur
= g_list_next(cur
)) {
2504 folder
= (Folder
*) cur
->data
;
2505 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
2506 prefs_matcher_write_func
, &data
);
2509 if (data
.error
== TRUE
)
2512 /* pre global rules */
2513 if (fprintf(fp
, "[preglobal]\n") < 0 ||
2514 prefs_filtering_write(fp
, pre_global_processing
) < 0 ||
2515 fputc('\n', fp
) == EOF
)
2518 /* post global rules */
2519 if (fprintf(fp
, "[postglobal]\n") < 0 ||
2520 prefs_filtering_write(fp
, post_global_processing
) < 0 ||
2521 fputc('\n', fp
) == EOF
)
2524 /* filtering rules */
2525 if (fprintf(fp
, "[filtering]\n") < 0 ||
2526 prefs_filtering_write(fp
, filtering_rules
) < 0 ||
2527 fputc('\n', fp
) == EOF
)
2534 *\brief Write filtering / matcher configuration file
2536 void prefs_matcher_write_config(void)
2541 debug_print("Writing matcher configuration...\n");
2543 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
2546 if ((pfile
= prefs_write_open(rcpath
)) == NULL
) {
2547 g_warning("failed to write configuration to file");
2554 if (prefs_matcher_save(pfile
->fp
) < 0) {
2555 g_warning("failed to write configuration to file");
2556 prefs_file_close_revert(pfile
);
2557 } else if (prefs_file_close(pfile
) < 0) {
2558 g_warning("failed to save configuration to file");
2563 *\brief Read matcher configuration
2565 void prefs_matcher_read_config(void)
2570 create_matchparser_hashtab();
2571 prefs_filtering_clear();
2573 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, MATCHER_RC
, NULL
);
2575 f
= g_fopen(rcpath
, "rb");
2579 matcher_parser_start_parsing(f
);
2580 fclose(matcher_parserin
);