2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2023 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/>.
21 #include "claws-features.h"
25 #include <glib/gi18n.h>
37 #include "procheader.h"
39 #include "matcher_parser.h"
40 #include "prefs_gtk.h"
41 #include "addr_compl.h"
43 #include "quoted-printable.h"
45 #include "prefs_common.h"
48 #include "folder_item_prefs.h"
50 #include "file-utils.h"
53 *\brief Keyword lookup element
56 gint id
; /*!< keyword id */
57 gchar
*str
; /*!< keyword */
59 typedef struct _MatchParser MatchParser
;
62 *\brief Table with strings and ids used by the lexer and
63 * the parser. New keywords can be added here.
65 static const MatchParser matchparser_tab
[] = {
67 {MATCHCRITERIA_ALL
, "all"},
68 {MATCHCRITERIA_UNREAD
, "unread"},
69 {MATCHCRITERIA_NOT_UNREAD
, "~unread"},
70 {MATCHCRITERIA_NEW
, "new"},
71 {MATCHCRITERIA_NOT_NEW
, "~new"},
72 {MATCHCRITERIA_MARKED
, "marked"},
73 {MATCHCRITERIA_NOT_MARKED
, "~marked"},
74 {MATCHCRITERIA_DELETED
, "deleted"},
75 {MATCHCRITERIA_NOT_DELETED
, "~deleted"},
76 {MATCHCRITERIA_REPLIED
, "replied"},
77 {MATCHCRITERIA_NOT_REPLIED
, "~replied"},
78 {MATCHCRITERIA_FORWARDED
, "forwarded"},
79 {MATCHCRITERIA_NOT_FORWARDED
, "~forwarded"},
80 {MATCHCRITERIA_LOCKED
, "locked"},
81 {MATCHCRITERIA_NOT_LOCKED
, "~locked"},
82 {MATCHCRITERIA_COLORLABEL
, "colorlabel"},
83 {MATCHCRITERIA_NOT_COLORLABEL
, "~colorlabel"},
84 {MATCHCRITERIA_IGNORE_THREAD
, "ignore_thread"},
85 {MATCHCRITERIA_NOT_IGNORE_THREAD
, "~ignore_thread"},
86 {MATCHCRITERIA_WATCH_THREAD
, "watch_thread"},
87 {MATCHCRITERIA_NOT_WATCH_THREAD
, "~watch_thread"},
88 {MATCHCRITERIA_SPAM
, "spam"},
89 {MATCHCRITERIA_NOT_SPAM
, "~spam"},
90 {MATCHCRITERIA_HAS_ATTACHMENT
, "has_attachment"},
91 {MATCHCRITERIA_HAS_NO_ATTACHMENT
, "~has_attachment"},
92 {MATCHCRITERIA_SIGNED
, "signed"},
93 {MATCHCRITERIA_NOT_SIGNED
, "~signed"},
96 {MATCHCRITERIA_SUBJECT
, "subject"},
97 {MATCHCRITERIA_NOT_SUBJECT
, "~subject"},
98 {MATCHCRITERIA_FROM
, "from"},
99 {MATCHCRITERIA_NOT_FROM
, "~from"},
100 {MATCHCRITERIA_TO
, "to"},
101 {MATCHCRITERIA_NOT_TO
, "~to"},
102 {MATCHCRITERIA_CC
, "cc"},
103 {MATCHCRITERIA_NOT_CC
, "~cc"},
104 {MATCHCRITERIA_TO_OR_CC
, "to_or_cc"},
105 {MATCHCRITERIA_NOT_TO_AND_NOT_CC
, "~to_or_cc"},
106 {MATCHCRITERIA_TAG
, "tag"},
107 {MATCHCRITERIA_NOT_TAG
, "~tag"},
108 {MATCHCRITERIA_TAGGED
, "tagged"},
109 {MATCHCRITERIA_NOT_TAGGED
, "~tagged"},
110 {MATCHCRITERIA_AGE_GREATER
, "age_greater"},
111 {MATCHCRITERIA_AGE_LOWER
, "age_lower"},
112 {MATCHCRITERIA_AGE_GREATER_HOURS
, "age_greater_hours"},
113 {MATCHCRITERIA_AGE_LOWER_HOURS
, "age_lower_hours"},
114 {MATCHCRITERIA_DATE_AFTER
, "date_after"},
115 {MATCHCRITERIA_DATE_BEFORE
, "date_before"},
116 {MATCHCRITERIA_NEWSGROUPS
, "newsgroups"},
117 {MATCHCRITERIA_NOT_NEWSGROUPS
, "~newsgroups"},
118 {MATCHCRITERIA_MESSAGEID
, "messageid"},
119 {MATCHCRITERIA_NOT_MESSAGEID
, "~messageid"},
120 {MATCHCRITERIA_INREPLYTO
, "inreplyto"},
121 {MATCHCRITERIA_NOT_INREPLYTO
, "~inreplyto"},
122 {MATCHCRITERIA_REFERENCES
, "references"},
123 {MATCHCRITERIA_NOT_REFERENCES
, "~references"},
124 {MATCHCRITERIA_SCORE_GREATER
, "score_greater"},
125 {MATCHCRITERIA_SCORE_LOWER
, "score_lower"},
126 {MATCHCRITERIA_SCORE_EQUAL
, "score_equal"},
127 {MATCHCRITERIA_PARTIAL
, "partial"},
128 {MATCHCRITERIA_NOT_PARTIAL
, "~partial"},
129 {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
, "found_in_addressbook"},
130 {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
, "~found_in_addressbook"},
132 {MATCHCRITERIA_SIZE_GREATER
, "size_greater"},
133 {MATCHCRITERIA_SIZE_SMALLER
, "size_smaller"},
134 {MATCHCRITERIA_SIZE_EQUAL
, "size_equal"},
136 /* content have to be read */
137 {MATCHCRITERIA_HEADER
, "header"},
138 {MATCHCRITERIA_NOT_HEADER
, "~header"},
139 {MATCHCRITERIA_HEADERS_PART
, "headers_part"},
140 {MATCHCRITERIA_NOT_HEADERS_PART
, "~headers_part"},
141 {MATCHCRITERIA_HEADERS_CONT
, "headers_cont"},
142 {MATCHCRITERIA_NOT_HEADERS_CONT
, "~headers_cont"},
143 {MATCHCRITERIA_MESSAGE
, "message"},
144 {MATCHCRITERIA_NOT_MESSAGE
, "~message"},
145 {MATCHCRITERIA_BODY_PART
, "body_part"},
146 {MATCHCRITERIA_NOT_BODY_PART
, "~body_part"},
147 {MATCHCRITERIA_TEST
, "test"},
148 {MATCHCRITERIA_NOT_TEST
, "~test"},
151 {MATCHTYPE_MATCHCASE
, "matchcase"},
152 {MATCHTYPE_MATCH
, "match"},
153 {MATCHTYPE_REGEXPCASE
, "regexpcase"},
154 {MATCHTYPE_REGEXP
, "regexp"},
157 {MATCHACTION_SCORE
, "score"}, /* for backward compatibility */
158 {MATCHACTION_MOVE
, "move"},
159 {MATCHACTION_COPY
, "copy"},
160 {MATCHACTION_DELETE
, "delete"},
161 {MATCHACTION_MARK
, "mark"},
162 {MATCHACTION_UNMARK
, "unmark"},
163 {MATCHACTION_LOCK
, "lock"},
164 {MATCHACTION_UNLOCK
, "unlock"},
165 {MATCHACTION_MARK_AS_READ
, "mark_as_read"},
166 {MATCHACTION_MARK_AS_UNREAD
, "mark_as_unread"},
167 {MATCHACTION_MARK_AS_SPAM
, "mark_as_spam"},
168 {MATCHACTION_MARK_AS_HAM
, "mark_as_ham"},
169 {MATCHACTION_FORWARD
, "forward"},
170 {MATCHACTION_FORWARD_AS_ATTACHMENT
, "forward_as_attachment"},
171 {MATCHACTION_EXECUTE
, "execute"},
172 {MATCHACTION_COLOR
, "color"},
173 {MATCHACTION_REDIRECT
, "redirect"},
174 {MATCHACTION_CHANGE_SCORE
, "change_score"},
175 {MATCHACTION_SET_SCORE
, "set_score"},
176 {MATCHACTION_STOP
, "stop"},
177 {MATCHACTION_HIDE
, "hide"},
178 {MATCHACTION_IGNORE
, "ignore"},
179 {MATCHACTION_WATCH
, "watch"},
180 {MATCHACTION_ADD_TO_ADDRESSBOOK
, "add_to_addressbook"},
181 {MATCHACTION_SET_TAG
, "set_tag"},
182 {MATCHACTION_UNSET_TAG
, "unset_tag"},
183 {MATCHACTION_CLEAR_TAGS
, "clear_tags"},
208 static gchar
*context_str
[N_CONTEXT_STRS
];
210 void matcher_init(void)
212 if (context_str
[CONTEXT_SUBJECT
] != NULL
)
215 context_str
[CONTEXT_SUBJECT
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Subject:"));
216 context_str
[CONTEXT_FROM
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
217 context_str
[CONTEXT_TO
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
218 context_str
[CONTEXT_CC
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
219 context_str
[CONTEXT_NEWSGROUPS
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Newsgroups:"));
220 context_str
[CONTEXT_MESSAGEID
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Message-ID:"));
221 context_str
[CONTEXT_IN_REPLY_TO
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("In-Reply-To:"));
222 context_str
[CONTEXT_REFERENCES
] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("References:"));
223 context_str
[CONTEXT_HEADER
] = g_strdup(_("header"));
224 context_str
[CONTEXT_HEADER_LINE
] = g_strdup(_("header line"));
225 context_str
[CONTEXT_BODY_LINE
] = g_strdup(_("body line"));
226 context_str
[CONTEXT_TAG
] = g_strdup(_("tag"));
229 void matcher_done(void)
232 for (i
= 0; i
< N_CONTEXT_STRS
; i
++) {
233 g_free(context_str
[i
]);
234 context_str
[i
] = NULL
;
238 extern gboolean debug_filtering_session
;
241 *\brief Look up table with keywords defined in \sa matchparser_tab
243 static GHashTable
*matchparser_hashtab
;
246 *\brief Translate keyword id to keyword string
248 *\param id Id of keyword
250 *\return const gchar * Keyword
252 const gchar
*get_matchparser_tab_str(gint id
)
256 for (i
= 0; i
< sizeof matchparser_tab
/ sizeof matchparser_tab
[0]; i
++) {
257 if (matchparser_tab
[i
].id
== id
)
258 return matchparser_tab
[i
].str
;
264 *\brief Create keyword lookup table
266 static void create_matchparser_hashtab(void)
270 if (matchparser_hashtab
) return;
271 matchparser_hashtab
= g_hash_table_new(g_str_hash
, g_str_equal
);
272 for (i
= 0; i
< sizeof matchparser_tab
/ sizeof matchparser_tab
[0]; i
++)
273 g_hash_table_insert(matchparser_hashtab
,
274 matchparser_tab
[i
].str
,
275 (gpointer
) &matchparser_tab
[i
]);
279 *\brief Return a keyword id from a keyword string
281 *\param str Keyword string
283 *\return gint Keyword id
285 gint
get_matchparser_tab_id(const gchar
*str
)
289 if (NULL
!= (res
= g_hash_table_lookup(matchparser_hashtab
, str
))) {
295 /* **************** data structure allocation **************** */
298 *\brief Allocate a structure for a filtering / scoring
299 * "condition" (a matcher structure)
301 *\param criteria Criteria ID (MATCHCRITERIA_XXXX)
302 *\param header Header string (if criteria is MATCHCRITERIA_HEADER
303 or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
304 *\param matchtype Type of action (MATCHTYPE_XXX)
305 *\param expr String value or expression to check
306 *\param value Integer value to check
308 *\return MatcherProp * Pointer to newly allocated structure
310 MatcherProp
*matcherprop_new(gint criteria
, const gchar
*header
,
311 gint matchtype
, const gchar
*expr
,
314 MatcherProp
*prop
= g_new0(MatcherProp
, 1);
316 prop
->criteria
= criteria
;
317 prop
->matchtype
= matchtype
;
320 prop
->header
= g_strdup(header
);
322 prop
->expr
= g_strdup(expr
);
327 *\brief Free a matcher structure
329 *\param prop Pointer to matcher structure allocated with
332 void matcherprop_free(MatcherProp
*prop
)
335 g_free(prop
->header
);
336 if (prop
->preg
!= NULL
) {
340 g_free(prop
->casefold_expr
);
345 *\brief Copy a matcher structure
347 *\param src Matcher structure to copy
349 *\return MatcherProp * Pointer to newly allocated matcher structure
351 MatcherProp
*matcherprop_copy(const MatcherProp
*src
)
353 MatcherProp
*prop
= g_new0(MatcherProp
, 1);
355 prop
->criteria
= src
->criteria
;
356 prop
->matchtype
= src
->matchtype
;
357 prop
->value
= src
->value
;
358 prop
->error
= src
->error
;
360 prop
->header
= g_strdup(src
->header
);
362 prop
->expr
= g_strdup(src
->expr
);
363 if (src
->casefold_expr
)
364 prop
->casefold_expr
= g_strdup(src
->casefold_expr
);
368 /* ************** match ******************************/
370 static gboolean match_with_addresses_in_addressbook
371 (MatcherProp
*prop
, GSList
*address_list
, gint type
,
372 gchar
* folderpath
, gint match
)
375 gboolean found
= FALSE
;
378 cm_return_val_if_fail(address_list
!= NULL
, FALSE
);
380 debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
381 g_slist_length(address_list
), folderpath
?folderpath
:"(null)");
383 if (folderpath
== NULL
||
384 strcasecmp(folderpath
, "Any") == 0 ||
390 start_address_completion(path
);
392 for (walk
= address_list
; walk
!= NULL
; walk
= walk
->next
) {
393 /* exact matching of email address */
394 guint num_addr
= complete_address(walk
->data
);
397 /* skip first item (this is the search string itself) */
399 for (; i
< num_addr
&& !found
; i
++) {
400 gchar
*addr
= get_complete_address(i
);
401 extract_address(addr
);
402 if (strcasecmp(addr
, walk
->data
) == 0) {
406 if (debug_filtering_session
407 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
408 log_print(LOG_DEBUG_FILTERING
,
409 "address [ %s ] matches\n",
410 (gchar
*)walk
->data
);
417 if (debug_filtering_session
418 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
420 log_print(LOG_DEBUG_FILTERING
,
421 "address [ %s ] does NOT match\n",
422 (gchar
*)walk
->data
);
426 if (match
== MATCH_ALL
) {
427 /* if matching all addresses, stop if one doesn't match */
430 if (debug_filtering_session
431 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
432 log_print(LOG_DEBUG_FILTERING
,
433 "not all address match (matching all)\n");
437 } else if (match
== MATCH_ANY
) {
438 /* if matching any address, stop if one does match */
441 if (debug_filtering_session
442 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
443 log_print(LOG_DEBUG_FILTERING
,
444 "at least one address matches (matching any)\n");
449 /* MATCH_ONE: there should be only one loop iteration */
452 end_address_completion();
458 *\brief Find out if a string matches a condition
460 *\param prop Matcher structure
461 *\param str String to check
463 *\return gboolean TRUE if str matches the condition in the
466 static gboolean
matcherprop_string_match(MatcherProp
*prop
, const gchar
*str
,
467 const gchar
*debug_context
)
470 const gchar
*down_expr
;
471 gboolean ret
= FALSE
;
472 gboolean should_free
= FALSE
;
476 if (prop
->matchtype
== MATCHTYPE_REGEXPCASE
||
477 prop
->matchtype
== MATCHTYPE_MATCHCASE
) {
478 str1
= g_utf8_casefold(str
, -1);
479 if (!prop
->casefold_expr
) {
480 prop
->casefold_expr
= g_utf8_casefold(prop
->expr
, -1);
482 down_expr
= prop
->casefold_expr
;
487 down_expr
= (gchar
*)prop
->expr
;
491 switch (prop
->matchtype
) {
492 case MATCHTYPE_REGEXPCASE
:
493 case MATCHTYPE_REGEXP
:
494 if (!prop
->preg
&& (prop
->error
== 0)) {
495 prop
->preg
= g_new0(regex_t
, 1);
496 /* if regexp then don't use the escaped string */
497 if (regcomp(prop
->preg
, down_expr
,
498 REG_NOSUB
| REG_EXTENDED
499 | ((prop
->matchtype
== MATCHTYPE_REGEXPCASE
)
500 ? REG_ICASE
: 0)) != 0) {
507 if (prop
->preg
== NULL
) {
512 if (regexec(prop
->preg
, str1
, 0, NULL
, 0) == 0)
518 if (debug_filtering_session
519 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
520 gchar
*stripped
= g_strdup(str
);
522 strretchomp(stripped
);
524 log_print(LOG_DEBUG_FILTERING
,
525 "%s value [ %s ] matches regular expression [ %s ] (%s)\n",
526 debug_context
, stripped
, prop
->expr
,
527 prop
->matchtype
== MATCHTYPE_REGEXP
? _("Case sensitive"):_("Case insensitive"));
529 log_print(LOG_DEBUG_FILTERING
,
530 "%s value [ %s ] does NOT match regular expression [ %s ] (%s)\n",
531 debug_context
, stripped
, prop
->expr
,
532 prop
->matchtype
== MATCHTYPE_REGEXP
? _("Case sensitive"):_("Case insensitive"));
537 case MATCHTYPE_MATCHCASE
:
538 case MATCHTYPE_MATCH
:
539 ret
= (strstr(str1
, down_expr
) != NULL
);
542 if (debug_filtering_session
543 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
544 gchar
*stripped
= g_strdup(str
);
546 strretchomp(stripped
);
548 log_print(LOG_DEBUG_FILTERING
,
549 "%s value [ %s ] contains [ %s ] (%s)\n",
550 debug_context
, stripped
, prop
->expr
,
551 prop
->matchtype
== MATCHTYPE_MATCH
? _("Case sensitive"):_("Case insensitive"));
553 log_print(LOG_DEBUG_FILTERING
,
554 "%s value [ %s ] does NOT contain [ %s ] (%s)\n",
555 debug_context
, stripped
, prop
->expr
,
556 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 successfully
684 static gboolean
matcherprop_match_test(const MatcherProp
*prop
,
692 thread_data
*td
= g_new0(thread_data
, 1);
694 time_t start_time
= time(NULL
);
697 file
= procmsg_get_message_file(info
);
706 cmd
= matching_build_command(prop
->expr
, info
);
716 if (debug_filtering_session
717 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
718 log_print(LOG_DEBUG_FILTERING
,
719 "starting threaded command [ %s ]\n",
725 if (pthread_create(&pt
, NULL
, matcher_test_thread
, td
) != 0)
726 retval
= system(cmd
);
728 debug_print("waiting for test thread\n");
730 /* don't let the interface freeze while waiting */
731 if (time(NULL
) - start_time
> 0) {
734 if (time(NULL
) - start_time
> 30) {
740 pthread_join(pt
, &res
);
741 retval
= GPOINTER_TO_INT(res
);
742 debug_print(" test thread returned %d\n", retval
);
747 if (debug_filtering_session
748 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
749 log_print(LOG_DEBUG_FILTERING
,
750 "starting synchronous command [ %s ]\n",
754 retval
= system(cmd
);
756 debug_print("Command exit code: %d\n", retval
);
759 if (debug_filtering_session
760 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
761 log_print(LOG_DEBUG_FILTERING
,
762 "command returned [ %d ]\n",
767 return (retval
== 0);
771 *\brief Check if a message matches the condition in a matcher
774 *\param prop Pointer to matcher structure
775 *\param info Pointer to message info
777 *\return gboolean TRUE if a match
779 static gboolean
matcherprop_match(MatcherProp
*prop
,
783 gint age_mult_hours
= 1;
785 switch(prop
->criteria
) {
786 case MATCHCRITERIA_ALL
:
788 case MATCHCRITERIA_UNREAD
:
789 return MSG_IS_UNREAD(info
->flags
);
790 case MATCHCRITERIA_NOT_UNREAD
:
791 return !MSG_IS_UNREAD(info
->flags
);
792 case MATCHCRITERIA_NEW
:
793 return MSG_IS_NEW(info
->flags
);
794 case MATCHCRITERIA_NOT_NEW
:
795 return !MSG_IS_NEW(info
->flags
);
796 case MATCHCRITERIA_MARKED
:
797 return MSG_IS_MARKED(info
->flags
);
798 case MATCHCRITERIA_NOT_MARKED
:
799 return !MSG_IS_MARKED(info
->flags
);
800 case MATCHCRITERIA_DELETED
:
801 return MSG_IS_DELETED(info
->flags
);
802 case MATCHCRITERIA_NOT_DELETED
:
803 return !MSG_IS_DELETED(info
->flags
);
804 case MATCHCRITERIA_REPLIED
:
805 return MSG_IS_REPLIED(info
->flags
);
806 case MATCHCRITERIA_NOT_REPLIED
:
807 return !MSG_IS_REPLIED(info
->flags
);
808 case MATCHCRITERIA_FORWARDED
:
809 return MSG_IS_FORWARDED(info
->flags
);
810 case MATCHCRITERIA_NOT_FORWARDED
:
811 return !MSG_IS_FORWARDED(info
->flags
);
812 case MATCHCRITERIA_LOCKED
:
813 return MSG_IS_LOCKED(info
->flags
);
814 case MATCHCRITERIA_NOT_LOCKED
:
815 return !MSG_IS_LOCKED(info
->flags
);
816 case MATCHCRITERIA_SPAM
:
817 return MSG_IS_SPAM(info
->flags
);
818 case MATCHCRITERIA_NOT_SPAM
:
819 return !MSG_IS_SPAM(info
->flags
);
820 case MATCHCRITERIA_HAS_ATTACHMENT
:
821 return MSG_IS_WITH_ATTACHMENT(info
->flags
);
822 case MATCHCRITERIA_HAS_NO_ATTACHMENT
:
823 return !MSG_IS_WITH_ATTACHMENT(info
->flags
);
824 case MATCHCRITERIA_SIGNED
:
825 return MSG_IS_SIGNED(info
->flags
);
826 case MATCHCRITERIA_NOT_SIGNED
:
827 return !MSG_IS_SIGNED(info
->flags
);
828 case MATCHCRITERIA_COLORLABEL
:
830 gint color
= MSG_GET_COLORLABEL_VALUE(info
->flags
);
831 gboolean ret
= (color
== prop
->value
);
834 if (debug_filtering_session
835 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
837 log_print(LOG_DEBUG_FILTERING
,
838 "message color value [ %d ] matches color value [ %d ]\n",
841 log_print(LOG_DEBUG_FILTERING
,
842 "message color value [ %d ] does NOT match color value [ %d ]\n",
848 case MATCHCRITERIA_NOT_COLORLABEL
:
850 gint color
= MSG_GET_COLORLABEL_VALUE(info
->flags
);
851 gboolean ret
= (color
!= prop
->value
);
854 if (debug_filtering_session
855 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
857 log_print(LOG_DEBUG_FILTERING
,
858 "message color value [ %d ] matches color value [ %d ]\n",
861 log_print(LOG_DEBUG_FILTERING
,
862 "message color value [ %d ] does NOT match color value [ %d ]\n",
868 case MATCHCRITERIA_IGNORE_THREAD
:
869 return MSG_IS_IGNORE_THREAD(info
->flags
);
870 case MATCHCRITERIA_NOT_IGNORE_THREAD
:
871 return !MSG_IS_IGNORE_THREAD(info
->flags
);
872 case MATCHCRITERIA_WATCH_THREAD
:
873 return MSG_IS_WATCH_THREAD(info
->flags
);
874 case MATCHCRITERIA_NOT_WATCH_THREAD
:
875 return !MSG_IS_WATCH_THREAD(info
->flags
);
876 case MATCHCRITERIA_SUBJECT
:
877 return matcherprop_string_match(prop
, info
->subject
, context_str
[CONTEXT_SUBJECT
]);
878 case MATCHCRITERIA_NOT_SUBJECT
:
879 return !matcherprop_string_match(prop
, info
->subject
, context_str
[CONTEXT_SUBJECT
]);
880 case MATCHCRITERIA_FROM
:
881 return matcherprop_string_match(prop
, info
->from
, context_str
[CONTEXT_FROM
]);
882 case MATCHCRITERIA_NOT_FROM
:
883 return !matcherprop_string_match(prop
, info
->from
, context_str
[CONTEXT_FROM
]);
884 case MATCHCRITERIA_TO
:
885 return matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
]);
886 case MATCHCRITERIA_NOT_TO
:
887 return !matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
]);
888 case MATCHCRITERIA_CC
:
889 return matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
890 case MATCHCRITERIA_NOT_CC
:
891 return !matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
892 case MATCHCRITERIA_TO_OR_CC
:
893 return matcherprop_string_match(prop
, info
->to
, context_str
[CONTEXT_TO
])
894 || matcherprop_string_match(prop
, info
->cc
, context_str
[CONTEXT_CC
]);
895 case MATCHCRITERIA_NOT_TO_AND_NOT_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_TAG
:
899 return matcherprop_tag_match(prop
, info
, context_str
[CONTEXT_TAG
]);
900 case MATCHCRITERIA_NOT_TAG
:
901 return !matcherprop_tag_match(prop
, info
, context_str
[CONTEXT_TAG
]);
902 case MATCHCRITERIA_TAGGED
:
903 return info
->tags
!= NULL
;
904 case MATCHCRITERIA_NOT_TAGGED
:
905 return info
->tags
== NULL
;
906 case MATCHCRITERIA_AGE_GREATER
:
908 /* Fallthrough intended */
909 case MATCHCRITERIA_AGE_GREATER_HOURS
:
915 age
= ((t
- info
->date_t
) / (60 * 60 * age_mult_hours
));
916 ret
= (age
>= prop
->value
);
919 if (debug_filtering_session
920 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
922 log_print(LOG_DEBUG_FILTERING
,
923 "message age [ %d ] is greater than [ %d ]\n",
926 log_print(LOG_DEBUG_FILTERING
,
927 "message age [ %d ] is not greater than [ %d ]\n",
933 case MATCHCRITERIA_DATE_AFTER
:
937 ret
= prop
->value
< info
->date_t
;
940 if (debug_filtering_session
941 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
943 log_print(LOG_DEBUG_FILTERING
,
944 "message date [ %" CM_TIME_FORMAT
" ] is after [ %d ]\n",
945 info
->date_t
, prop
->value
);
947 log_print(LOG_DEBUG_FILTERING
,
948 "message date [ %" CM_TIME_FORMAT
" ] is not after [ %d ]\n",
949 info
->date_t
, prop
->value
);
954 case MATCHCRITERIA_AGE_LOWER
:
956 /* Fallthrough intended */
957 case MATCHCRITERIA_AGE_LOWER_HOURS
:
963 age
= ((t
- info
->date_t
) / (60 * 60 * age_mult_hours
));
964 ret
= (age
< prop
->value
);
967 if (debug_filtering_session
968 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
970 log_print(LOG_DEBUG_FILTERING
,
971 "message age [ %d ] is lower than [ %d ]\n",
974 log_print(LOG_DEBUG_FILTERING
,
975 "message age [ %d ] is not lower than [ %d ]\n",
981 case MATCHCRITERIA_DATE_BEFORE
:
985 ret
= prop
->value
> info
->date_t
;
988 if (debug_filtering_session
989 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
991 log_print(LOG_DEBUG_FILTERING
,
992 "message date [ %" CM_TIME_FORMAT
" ] is before [ %d ]\n",
993 info
->date_t
, prop
->value
);
995 log_print(LOG_DEBUG_FILTERING
,
996 "message date [ %" CM_TIME_FORMAT
" ] is not before [ %d ]\n",
997 info
->date_t
, prop
->value
);
1002 case MATCHCRITERIA_SCORE_GREATER
:
1004 gboolean ret
= (info
->score
> prop
->value
);
1007 if (debug_filtering_session
1008 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1010 log_print(LOG_DEBUG_FILTERING
,
1011 "message score [ %d ] is greater than [ %d ]\n",
1012 info
->score
, prop
->value
);
1014 log_print(LOG_DEBUG_FILTERING
,
1015 "message score [ %d ] is not greater than [ %d ]\n",
1016 info
->score
, prop
->value
);
1021 case MATCHCRITERIA_SCORE_LOWER
:
1023 gboolean ret
= (info
->score
< 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 score [ %d ] is lower than [ %d ]\n",
1031 info
->score
, prop
->value
);
1033 log_print(LOG_DEBUG_FILTERING
,
1034 "message score [ %d ] is not lower than [ %d ]\n",
1035 info
->score
, prop
->value
);
1040 case MATCHCRITERIA_SCORE_EQUAL
:
1042 gboolean ret
= (info
->score
== prop
->value
);
1045 if (debug_filtering_session
1046 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1048 log_print(LOG_DEBUG_FILTERING
,
1049 "message score [ %d ] is equal to [ %d ]\n",
1050 info
->score
, prop
->value
);
1052 log_print(LOG_DEBUG_FILTERING
,
1053 "message score [ %d ] is not equal to [ %d ]\n",
1054 info
->score
, prop
->value
);
1059 case MATCHCRITERIA_SIZE_GREATER
:
1061 /* FIXME: info->size is a goffset */
1062 gboolean ret
= (info
->size
> (goffset
) prop
->value
);
1065 if (debug_filtering_session
1066 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1068 log_print(LOG_DEBUG_FILTERING
,
1069 "message size is greater than [ %d ]\n",
1072 log_print(LOG_DEBUG_FILTERING
,
1073 "message size is not greater than [ %d ]\n",
1079 case MATCHCRITERIA_SIZE_SMALLER
:
1081 /* FIXME: info->size is a goffset */
1082 gboolean ret
= (info
->size
< (goffset
) prop
->value
);
1085 if (debug_filtering_session
1086 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1088 log_print(LOG_DEBUG_FILTERING
,
1089 "message size is smaller than [ %d ]\n",
1092 log_print(LOG_DEBUG_FILTERING
,
1093 "message size is not smaller than [ %d ]\n",
1099 case MATCHCRITERIA_SIZE_EQUAL
:
1101 /* FIXME: info->size is a goffset */
1102 gboolean ret
= (info
->size
== (goffset
) prop
->value
);
1105 if (debug_filtering_session
1106 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1108 log_print(LOG_DEBUG_FILTERING
,
1109 "message size is equal to [ %d ]\n",
1112 log_print(LOG_DEBUG_FILTERING
,
1113 "message size is not equal to [ %d ]\n",
1119 case MATCHCRITERIA_PARTIAL
:
1121 /* FIXME: info->size is a goffset */
1122 gboolean ret
= (info
->total_size
!= 0 && info
->size
!= (goffset
)info
->total_size
);
1125 if (debug_filtering_session
1126 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1128 log_print(LOG_DEBUG_FILTERING
,
1129 "message is partially downloaded, size is less than total size [ %d ])\n",
1132 log_print(LOG_DEBUG_FILTERING
,
1133 "message is not partially downloaded\n");
1138 case MATCHCRITERIA_NOT_PARTIAL
:
1140 /* FIXME: info->size is a goffset */
1141 gboolean ret
= (info
->total_size
== 0 || info
->size
== (goffset
)info
->total_size
);
1144 if (debug_filtering_session
1145 && prefs_common
.filtering_debug_level
>= FILTERING_DEBUG_LEVEL_HIGH
) {
1147 log_print(LOG_DEBUG_FILTERING
,
1148 "message is not partially downloaded\n");
1150 log_print(LOG_DEBUG_FILTERING
,
1151 "message is partially downloaded, size is less than total size [ %d ])\n",
1157 case MATCHCRITERIA_NEWSGROUPS
:
1158 return matcherprop_string_match(prop
, info
->newsgroups
, context_str
[CONTEXT_NEWSGROUPS
]);
1159 case MATCHCRITERIA_NOT_NEWSGROUPS
:
1160 return !matcherprop_string_match(prop
, info
->newsgroups
, context_str
[CONTEXT_NEWSGROUPS
]);
1161 case MATCHCRITERIA_MESSAGEID
:
1162 return matcherprop_string_match(prop
, info
->msgid
, context_str
[CONTEXT_MESSAGEID
]);
1163 case MATCHCRITERIA_NOT_MESSAGEID
:
1164 return !matcherprop_string_match(prop
, info
->msgid
, context_str
[CONTEXT_MESSAGEID
]);
1165 case MATCHCRITERIA_INREPLYTO
:
1166 return matcherprop_string_match(prop
, info
->inreplyto
, context_str
[CONTEXT_IN_REPLY_TO
]);
1167 case MATCHCRITERIA_NOT_INREPLYTO
:
1168 return !matcherprop_string_match(prop
, info
->inreplyto
, context_str
[CONTEXT_IN_REPLY_TO
]);
1169 case MATCHCRITERIA_REFERENCES
:
1170 return matcherprop_list_match(prop
, info
->references
, context_str
[CONTEXT_REFERENCES
]);
1171 case MATCHCRITERIA_NOT_REFERENCES
:
1172 return !matcherprop_list_match(prop
, info
->references
, context_str
[CONTEXT_REFERENCES
]);
1173 case MATCHCRITERIA_TEST
:
1174 return matcherprop_match_test(prop
, info
);
1175 case MATCHCRITERIA_NOT_TEST
:
1176 return !matcherprop_match_test(prop
, info
);
1182 /* ********************* MatcherList *************************** */
1185 *\brief Create a new list of matchers
1187 *\param matchers List of matcher structures
1188 *\param bool_and Operator
1190 *\return MatcherList * New list
1192 MatcherList
*matcherlist_new(GSList
*matchers
, gboolean bool_and
)
1196 cond
= g_new0(MatcherList
, 1);
1198 cond
->matchers
= matchers
;
1199 cond
->bool_and
= bool_and
;
1206 *\brief Builds a single regular expresion from an array of srings.
1208 *\param strings The lines containing the different sub-regexp.
1210 *\return The newly allocated regexp string.
1212 static gchar
*build_complete_regexp(gchar
**strings
)
1216 while (strings
&& strings
[i
] && *strings
[i
]) {
1217 int old_len
= expr
? strlen(expr
):0;
1219 gchar
*tmpstr
= NULL
;
1221 if (g_utf8_validate(strings
[i
], -1, NULL
))
1222 tmpstr
= g_strdup(strings
[i
]);
1224 tmpstr
= conv_codeset_strdup(strings
[i
],
1225 conv_get_locale_charset_str_no_utf8(),
1228 if (strstr(tmpstr
, "\n"))
1229 *(strstr(tmpstr
, "\n")) = '\0';
1231 new_len
= strlen(tmpstr
);
1233 expr
= g_realloc(expr
,
1234 expr
? (old_len
+ strlen("|()") + new_len
+ 1)
1235 : (strlen("()") + new_len
+ 1));
1238 strcpy(expr
+ old_len
, "|(");
1239 strcpy(expr
+ old_len
+ 2, tmpstr
);
1240 strcpy(expr
+ old_len
+ 2 + new_len
, ")");
1242 strcpy(expr
+old_len
, "(");
1243 strcpy(expr
+old_len
+ 1, tmpstr
);
1244 strcpy(expr
+old_len
+ 1 + new_len
, ")");
1254 *\brief Create a new list of matchers from a multi-line string
1256 *\param lines String with "\n"-separated expressions
1257 *\param bool_and Operator
1258 *\param case_sensitive If the matching is case sensitive or not
1260 *\return MatcherList * New matcher list
1262 MatcherList
*matcherlist_new_from_lines(gchar
*lines
, gboolean bool_and
,
1263 gboolean case_sensitive
)
1265 MatcherProp
*m
= NULL
;
1266 GSList
*matchers
= NULL
;
1267 gchar
**strings
= g_strsplit(lines
, "\n", -1);
1271 expr
= build_complete_regexp(strings
);
1272 debug_print("building matcherprop for expr '%s'\n", expr
?expr
:"NULL");
1274 m
= matcherprop_new(MATCHCRITERIA_SUBJECT
, NULL
,
1275 case_sensitive
? MATCHTYPE_REGEXP
: MATCHTYPE_REGEXPCASE
,
1278 /* print error message */
1279 debug_print("failed to allocate memory for matcherprop\n");
1281 matchers
= g_slist_append(matchers
, m
);
1287 while (strings
&& strings
[i
] && *strings
[i
]) {
1288 m
= matcherprop_new(MATCHCRITERIA_SUBJECT
, NULL
,
1289 case_sensitive
? MATCHTYPE_MATCH
: MATCHTYPE_MATCHCASE
,
1292 /* print error message */
1293 debug_print("failed to allocate memory for matcherprop\n");
1295 matchers
= g_slist_append(matchers
, m
);
1300 g_strfreev(strings
);
1302 return matcherlist_new(matchers
, bool_and
);
1306 *\brief Frees a list of matchers
1308 *\param cond List of matchers
1310 void matcherlist_free(MatcherList
*cond
)
1314 cm_return_if_fail(cond
);
1315 for (l
= cond
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1316 matcherprop_free((MatcherProp
*) l
->data
);
1318 g_slist_free(cond
->matchers
);
1323 *\brief Check if a header matches a matcher condition
1325 *\param matcher Matcher structure to check header for
1326 *\param buf Header name
1328 *\return boolean TRUE if matching header
1330 static gboolean
matcherprop_match_one_header(MatcherProp
*matcher
,
1333 gboolean result
= FALSE
;
1334 Header
*header
= NULL
;
1336 switch (matcher
->criteria
) {
1337 case MATCHCRITERIA_HEADER
:
1338 case MATCHCRITERIA_NOT_HEADER
:
1339 header
= procheader_parse_header(buf
);
1342 if (procheader_headername_equal(header
->name
,
1344 if (matcher
->criteria
== MATCHCRITERIA_HEADER
)
1345 result
= matcherprop_string_match(matcher
, header
->body
, context_str
[CONTEXT_HEADER
]);
1347 result
= !matcherprop_string_match(matcher
, header
->body
, context_str
[CONTEXT_HEADER
]);
1348 procheader_header_free(header
);
1352 procheader_header_free(header
);
1355 case MATCHCRITERIA_HEADERS_PART
:
1356 case MATCHCRITERIA_HEADERS_CONT
:
1357 case MATCHCRITERIA_MESSAGE
:
1358 header
= procheader_parse_header(buf
);
1361 result
= matcherprop_header_line_match(matcher
,
1362 header
->name
, header
->body
,
1363 (matcher
->criteria
== MATCHCRITERIA_HEADERS_PART
),
1364 context_str
[CONTEXT_HEADER_LINE
]);
1365 procheader_header_free(header
);
1367 case MATCHCRITERIA_NOT_HEADERS_CONT
:
1368 case MATCHCRITERIA_NOT_HEADERS_PART
:
1369 case MATCHCRITERIA_NOT_MESSAGE
:
1370 header
= procheader_parse_header(buf
);
1373 result
= !matcherprop_header_line_match(matcher
,
1374 header
->name
, header
->body
,
1375 (matcher
->criteria
== MATCHCRITERIA_NOT_HEADERS_PART
),
1376 context_str
[CONTEXT_HEADER_LINE
]);
1377 procheader_header_free(header
);
1379 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
:
1380 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
:
1382 GSList
*address_list
= NULL
;
1383 gint match
= MATCH_ONE
;
1384 gboolean found
= FALSE
;
1386 /* how many address headers are we trying to match? */
1387 if (strcasecmp(matcher
->header
, "Any") == 0)
1389 else if (strcasecmp(matcher
->header
, "All") == 0)
1392 if (match
== MATCH_ONE
) {
1393 /* matching one address header exactly, is that the right one? */
1394 header
= procheader_parse_header(buf
);
1396 !procheader_headername_equal(header
->name
, matcher
->header
)) {
1397 procheader_header_free(header
);
1400 address_list
= address_list_append(address_list
, header
->body
);
1401 if (address_list
== NULL
) {
1402 procheader_header_free(header
);
1405 procheader_header_free(header
);
1408 header
= procheader_parse_header(buf
);
1411 /* address header is one of the headers we have to match when checking
1412 for any address header or all address headers? */
1413 if (procheader_headername_equal(header
->name
, "From") ||
1414 procheader_headername_equal(header
->name
, "To") ||
1415 procheader_headername_equal(header
->name
, "Cc") ||
1416 procheader_headername_equal(header
->name
, "Reply-To") ||
1417 procheader_headername_equal(header
->name
, "Sender") ||
1418 procheader_headername_equal(header
->name
, "Resent-From") ||
1419 procheader_headername_equal(header
->name
, "Resent-To"))
1420 address_list
= address_list_append(address_list
, header
->body
);
1421 procheader_header_free(header
);
1422 if (address_list
== NULL
)
1426 found
= match_with_addresses_in_addressbook
1427 (matcher
, address_list
, matcher
->criteria
,
1428 matcher
->expr
, match
);
1429 g_slist_free(address_list
);
1431 if (matcher
->criteria
== MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
)
1442 *\brief Check if the matcher structure wants headers to
1445 *\param matcher Matcher structure
1447 *\return gboolean TRUE if the matcher structure describes
1448 * a header match condition
1450 static gboolean
matcherprop_criteria_headers(const MatcherProp
*matcher
)
1452 switch (matcher
->criteria
) {
1453 case MATCHCRITERIA_HEADER
:
1454 case MATCHCRITERIA_NOT_HEADER
:
1455 case MATCHCRITERIA_HEADERS_PART
:
1456 case MATCHCRITERIA_HEADERS_CONT
:
1457 case MATCHCRITERIA_NOT_HEADERS_PART
:
1458 case MATCHCRITERIA_NOT_HEADERS_CONT
:
1459 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
:
1460 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
:
1468 *\brief Check if the matcher structure wants the message
1469 * to be matched (just perform an action on any
1472 *\param matcher Matcher structure
1474 *\return gboolean TRUE if matcher condition should match
1477 static gboolean
matcherprop_criteria_message(MatcherProp
*matcher
)
1479 switch (matcher
->criteria
) {
1480 case MATCHCRITERIA_MESSAGE
:
1481 case MATCHCRITERIA_NOT_MESSAGE
:
1489 *\brief Check if a list of conditions matches one header in
1492 *\param matchers List of conditions
1493 *\param fp Message file
1495 *\return gboolean TRUE if one of the headers is matched by
1496 * the list of conditions.
1498 static gboolean
matcherlist_match_headers(MatcherList
*matchers
, FILE *fp
)
1504 while ((ret
= procheader_get_one_field(&buf
, fp
, NULL
)) != -1) {
1505 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1506 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1507 gint match
= MATCH_ANY
;
1512 /* determine the match range (all, any are our concern here) */
1513 if (matcher
->criteria
== MATCHCRITERIA_NOT_HEADERS_PART
||
1514 matcher
->criteria
== MATCHCRITERIA_NOT_HEADERS_CONT
||
1515 matcher
->criteria
== MATCHCRITERIA_NOT_MESSAGE
) {
1518 } else if (matcher
->criteria
== MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
||
1519 matcher
->criteria
== MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
) {
1520 Header
*header
= NULL
;
1522 /* address header is one of the headers we have to match when checking
1523 for any address header or all address headers? */
1524 header
= procheader_parse_header(buf
);
1526 (procheader_headername_equal(header
->name
, "From") ||
1527 procheader_headername_equal(header
->name
, "To") ||
1528 procheader_headername_equal(header
->name
, "Cc") ||
1529 procheader_headername_equal(header
->name
, "Reply-To") ||
1530 procheader_headername_equal(header
->name
, "Sender") ||
1531 procheader_headername_equal(header
->name
, "Resent-From") ||
1532 procheader_headername_equal(header
->name
, "Resent-To"))) {
1534 if (strcasecmp(matcher
->header
, "Any") == 0)
1536 else if (strcasecmp(matcher
->header
, "All") == 0)
1543 /* matching one address header exactly, is that the right one?
1544 further call to matcherprop_match_one_header() will tell us */
1546 procheader_header_free(header
);
1549 /* ZERO line must NOT match for the rule to match.
1551 if (match
== MATCH_ALL
) {
1552 if (matcherprop_match_one_header(matcher
, buf
)) {
1553 matcher
->result
= TRUE
;
1555 matcher
->result
= FALSE
;
1556 matcher
->done
= TRUE
;
1558 /* else, just one line matching is enough for the rule to match
1560 } else if (matcherprop_criteria_headers(matcher
) ||
1561 matcherprop_criteria_message(matcher
)) {
1562 if (matcherprop_match_one_header(matcher
, buf
)) {
1563 matcher
->result
= TRUE
;
1564 matcher
->done
= TRUE
;
1568 /* if the rule matched and the matchers are OR, no need to
1569 * check the others */
1570 if (matcher
->result
&& matcher
->done
) {
1571 if (!matchers
->bool_and
) {
1585 *\brief Check if a matcher wants to check the message body
1587 *\param matcher Matcher structure
1589 *\return gboolean TRUE if body must be matched.
1591 static gboolean
matcherprop_criteria_body(const MatcherProp
*matcher
)
1593 switch (matcher
->criteria
) {
1594 case MATCHCRITERIA_BODY_PART
:
1595 case MATCHCRITERIA_NOT_BODY_PART
:
1602 static gboolean
matcherlist_match_binary_content(MatcherList
*matchers
, MimeInfo
*partinfo
)
1605 gchar buf
[BUFFSIZE
];
1608 if (!partinfo
|| partinfo
->type
== MIMETYPE_TEXT
)
1611 outfp
= procmime_get_binary_content(partinfo
);
1616 while (claws_fgets(buf
, sizeof(buf
), outfp
) != NULL
) {
1619 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1620 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1625 /* Don't scan non-text parts when looking in body, only
1626 * when looking in whole message
1628 if (matcher
->criteria
== MATCHCRITERIA_NOT_BODY_PART
||
1629 matcher
->criteria
== MATCHCRITERIA_BODY_PART
)
1632 /* if the criteria is ~body_part or ~message, ZERO lines
1633 * must match for the rule to match.
1635 if (matcher
->criteria
== MATCHCRITERIA_NOT_BODY_PART
||
1636 matcher
->criteria
== MATCHCRITERIA_NOT_MESSAGE
) {
1637 if (matcherprop_string_match(matcher
, buf
,
1638 context_str
[CONTEXT_BODY_LINE
])) {
1639 matcher
->result
= FALSE
;
1640 matcher
->done
= TRUE
;
1642 matcher
->result
= TRUE
;
1643 /* else, just one line has to match */
1644 } else if (matcherprop_criteria_body(matcher
) ||
1645 matcherprop_criteria_message(matcher
)) {
1646 if (matcherprop_string_match(matcher
, buf
,
1647 context_str
[CONTEXT_BODY_LINE
])) {
1648 matcher
->result
= TRUE
;
1649 matcher
->done
= TRUE
;
1653 /* if the matchers are OR'ed and the rule matched,
1654 * no need to check the others. */
1655 if (matcher
->result
&& matcher
->done
) {
1656 if (!matchers
->bool_and
) {
1657 claws_fclose(outfp
);
1664 claws_fclose(outfp
);
1668 static gboolean
match_content_cb(const gchar
*buf
, gpointer data
)
1670 MatcherList
*matchers
= (MatcherList
*)data
;
1671 gboolean all_done
= TRUE
;
1674 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1675 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1680 /* if the criteria is ~body_part or ~message, ZERO lines
1681 * must match for the rule to match.
1683 if (matcher
->criteria
== MATCHCRITERIA_NOT_BODY_PART
||
1684 matcher
->criteria
== MATCHCRITERIA_NOT_MESSAGE
) {
1685 if (matcherprop_string_match(matcher
, buf
,
1686 context_str
[CONTEXT_BODY_LINE
])) {
1687 matcher
->result
= FALSE
;
1688 matcher
->done
= TRUE
;
1690 matcher
->result
= TRUE
;
1691 /* else, just one line has to match */
1692 } else if (matcherprop_criteria_body(matcher
) ||
1693 matcherprop_criteria_message(matcher
)) {
1694 if (matcherprop_string_match(matcher
, buf
,
1695 context_str
[CONTEXT_BODY_LINE
])) {
1696 matcher
->result
= TRUE
;
1697 matcher
->done
= TRUE
;
1701 /* if the matchers are OR'ed and the rule matched,
1702 * no need to check the others. */
1703 if (matcher
->result
&& matcher
->done
) {
1704 if (!matchers
->bool_and
) {
1715 static gboolean
matcherlist_match_text_content(MatcherList
*matchers
, MimeInfo
*partinfo
)
1717 if (partinfo
->type
!= MIMETYPE_TEXT
)
1720 return procmime_scan_text_content(partinfo
, match_content_cb
, matchers
);
1724 *\brief Check if a line in a message file's body matches
1727 *\param matchers List of conditions
1728 *\param fp Message file
1730 *\return gboolean TRUE if successful match
1732 static gboolean
matcherlist_match_body(MatcherList
*matchers
, gboolean body_only
, MsgInfo
*info
)
1734 MimeInfo
*mimeinfo
= NULL
;
1735 MimeInfo
*partinfo
= NULL
;
1736 gboolean first_text_found
= FALSE
;
1738 cm_return_val_if_fail(info
!= NULL
, FALSE
);
1740 mimeinfo
= procmime_scan_message(info
);
1743 partinfo
= procmime_mimeinfo_next(mimeinfo
);
1745 for (; partinfo
!= NULL
; partinfo
= procmime_mimeinfo_next(partinfo
)) {
1747 if (partinfo
->type
!= MIMETYPE_TEXT
&& body_only
)
1750 if (partinfo
->type
== MIMETYPE_TEXT
) {
1751 first_text_found
= TRUE
;
1752 if (matcherlist_match_text_content(matchers
, partinfo
)) {
1753 procmime_mimeinfo_free_all(&mimeinfo
);
1756 } else if (matcherlist_match_binary_content(matchers
, partinfo
)) {
1757 procmime_mimeinfo_free_all(&mimeinfo
);
1761 if (body_only
&& first_text_found
)
1764 procmime_mimeinfo_free_all(&mimeinfo
);
1770 *\brief Check if a message file matches criteria
1772 *\param matchers Criteria
1773 *\param info Message info
1774 *\param result Default result
1776 *\return gboolean TRUE if matched
1778 static gboolean
matcherlist_match_file(MatcherList
*matchers
, MsgInfo
*info
,
1781 gboolean read_headers
;
1788 /* file need to be read ? */
1790 read_headers
= FALSE
;
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 read_headers
= TRUE
;
1798 if (matcherprop_criteria_body(matcher
))
1800 if (matcherprop_criteria_message(matcher
)) {
1801 read_headers
= TRUE
;
1805 matcher
->result
= FALSE
;
1806 matcher
->done
= FALSE
;
1809 if (!read_headers
&& !read_body
)
1812 file
= procmsg_get_message_file_full(info
, read_headers
, read_body
);
1816 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
1817 FILE_OP_ERROR(file
, "claws_fopen");
1822 /* read the headers */
1825 if (matcherlist_match_headers(matchers
, fp
))
1828 procheader_skip_headers(fp
);
1833 matcherlist_match_body(matchers
, body_only
, info
);
1836 for (l
= matchers
->matchers
; l
!= NULL
; l
= g_slist_next(l
)) {
1837 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1839 if (matcherprop_criteria_headers(matcher
) ||
1840 matcherprop_criteria_body(matcher
) ||
1841 matcherprop_criteria_message(matcher
)) {
1842 if (matcher
->result
) {
1843 if (!matchers
->bool_and
) {
1849 if (matchers
->bool_and
) {
1865 *\brief Test list of conditions on a message.
1867 *\param matchers List of conditions
1868 *\param info Message info
1870 *\return gboolean TRUE if matched
1872 gboolean
matcherlist_match(MatcherList
*matchers
, MsgInfo
*info
)
1880 if (matchers
->bool_and
)
1885 /* test the cached elements */
1887 for (l
= matchers
->matchers
; l
!= NULL
;l
= g_slist_next(l
)) {
1888 MatcherProp
*matcher
= (MatcherProp
*) l
->data
;
1890 if (debug_filtering_session
) {
1891 gchar
*buf
= matcherprop_to_string(matcher
);
1892 log_print(LOG_DEBUG_FILTERING
, _("checking if message matches [ %s ]\n"), buf
);
1896 switch(matcher
->criteria
) {
1897 case MATCHCRITERIA_ALL
:
1898 case MATCHCRITERIA_UNREAD
:
1899 case MATCHCRITERIA_NOT_UNREAD
:
1900 case MATCHCRITERIA_NEW
:
1901 case MATCHCRITERIA_NOT_NEW
:
1902 case MATCHCRITERIA_MARKED
:
1903 case MATCHCRITERIA_NOT_MARKED
:
1904 case MATCHCRITERIA_DELETED
:
1905 case MATCHCRITERIA_NOT_DELETED
:
1906 case MATCHCRITERIA_REPLIED
:
1907 case MATCHCRITERIA_NOT_REPLIED
:
1908 case MATCHCRITERIA_FORWARDED
:
1909 case MATCHCRITERIA_NOT_FORWARDED
:
1910 case MATCHCRITERIA_LOCKED
:
1911 case MATCHCRITERIA_NOT_LOCKED
:
1912 case MATCHCRITERIA_SPAM
:
1913 case MATCHCRITERIA_NOT_SPAM
:
1914 case MATCHCRITERIA_HAS_ATTACHMENT
:
1915 case MATCHCRITERIA_HAS_NO_ATTACHMENT
:
1916 case MATCHCRITERIA_SIGNED
:
1917 case MATCHCRITERIA_NOT_SIGNED
:
1918 case MATCHCRITERIA_COLORLABEL
:
1919 case MATCHCRITERIA_NOT_COLORLABEL
:
1920 case MATCHCRITERIA_IGNORE_THREAD
:
1921 case MATCHCRITERIA_NOT_IGNORE_THREAD
:
1922 case MATCHCRITERIA_WATCH_THREAD
:
1923 case MATCHCRITERIA_NOT_WATCH_THREAD
:
1924 case MATCHCRITERIA_SUBJECT
:
1925 case MATCHCRITERIA_NOT_SUBJECT
:
1926 case MATCHCRITERIA_FROM
:
1927 case MATCHCRITERIA_NOT_FROM
:
1928 case MATCHCRITERIA_TO
:
1929 case MATCHCRITERIA_NOT_TO
:
1930 case MATCHCRITERIA_CC
:
1931 case MATCHCRITERIA_NOT_CC
:
1932 case MATCHCRITERIA_TO_OR_CC
:
1933 case MATCHCRITERIA_NOT_TO_AND_NOT_CC
:
1934 case MATCHCRITERIA_TAG
:
1935 case MATCHCRITERIA_NOT_TAG
:
1936 case MATCHCRITERIA_TAGGED
:
1937 case MATCHCRITERIA_NOT_TAGGED
:
1938 case MATCHCRITERIA_AGE_GREATER
:
1939 case MATCHCRITERIA_AGE_LOWER
:
1940 case MATCHCRITERIA_AGE_GREATER_HOURS
:
1941 case MATCHCRITERIA_AGE_LOWER_HOURS
:
1942 case MATCHCRITERIA_DATE_AFTER
:
1943 case MATCHCRITERIA_DATE_BEFORE
:
1944 case MATCHCRITERIA_NEWSGROUPS
:
1945 case MATCHCRITERIA_NOT_NEWSGROUPS
:
1946 case MATCHCRITERIA_MESSAGEID
:
1947 case MATCHCRITERIA_NOT_MESSAGEID
:
1948 case MATCHCRITERIA_INREPLYTO
:
1949 case MATCHCRITERIA_NOT_INREPLYTO
:
1950 case MATCHCRITERIA_REFERENCES
:
1951 case MATCHCRITERIA_NOT_REFERENCES
:
1952 case MATCHCRITERIA_SCORE_GREATER
:
1953 case MATCHCRITERIA_SCORE_LOWER
:
1954 case MATCHCRITERIA_SCORE_EQUAL
:
1955 case MATCHCRITERIA_SIZE_GREATER
:
1956 case MATCHCRITERIA_SIZE_SMALLER
:
1957 case MATCHCRITERIA_SIZE_EQUAL
:
1958 case MATCHCRITERIA_TEST
:
1959 case MATCHCRITERIA_NOT_TEST
:
1960 case MATCHCRITERIA_PARTIAL
:
1961 case MATCHCRITERIA_NOT_PARTIAL
:
1962 if (matcherprop_match(matcher
, info
)) {
1963 if (!matchers
->bool_and
) {
1964 if (debug_filtering_session
)
1965 log_status_ok(LOG_DEBUG_FILTERING
, _("message matches\n"));
1970 if (matchers
->bool_and
) {
1971 if (debug_filtering_session
)
1972 log_status_nok(LOG_DEBUG_FILTERING
, _("message does not match\n"));
1979 /* test the condition on the file */
1981 if (matcherlist_match_file(matchers
, info
, result
)) {
1982 if (!matchers
->bool_and
) {
1983 if (debug_filtering_session
)
1984 log_status_ok(LOG_DEBUG_FILTERING
, _("message matches\n"));
1988 if (matchers
->bool_and
) {
1989 if (debug_filtering_session
)
1990 log_status_nok(LOG_DEBUG_FILTERING
, _("message does not match\n"));
1995 if (debug_filtering_session
) {
1997 log_status_ok(LOG_DEBUG_FILTERING
, _("message matches\n"));
1999 log_status_nok(LOG_DEBUG_FILTERING
, _("message does not match\n"));
2005 static gint
quote_filter_str(gchar
* result
, guint size
,
2015 for(p
= path
; * p
!= '\0' ; p
++) {
2017 if ((* p
!= '\"') && (* p
!= '\\')) {
2018 if (remaining
> 0) {
2024 result
[size
- 1] = '\0';
2029 if (remaining
>= 2) {
2037 result
[size
- 1] = '\0';
2042 if (remaining
> 0) {
2046 result
[size
- 1] = '\0';
2054 gchar
* matcher_quote_str(const gchar
* src
)
2059 len
= strlen(src
) * 2 + 1;
2060 res
= g_malloc(len
);
2061 quote_filter_str(res
, len
, src
);
2067 *\brief Convert a matcher structure to a string
2069 *\param matcher Matcher structure
2071 *\return gchar * Newly allocated string
2073 gchar
*matcherprop_to_string(MatcherProp
*matcher
)
2075 gchar
*matcher_str
= NULL
;
2076 const gchar
*criteria_str
;
2077 const gchar
*matchtype_str
;
2079 gchar
* quoted_expr
;
2080 gchar
* quoted_header
;
2082 criteria_str
= NULL
;
2083 for (i
= 0; i
< (int) (sizeof(matchparser_tab
) / sizeof(MatchParser
)); i
++) {
2084 if (matchparser_tab
[i
].id
== matcher
->criteria
)
2085 criteria_str
= matchparser_tab
[i
].str
;
2087 if (criteria_str
== NULL
)
2090 switch (matcher
->criteria
) {
2091 case MATCHCRITERIA_AGE_GREATER
:
2092 case MATCHCRITERIA_AGE_LOWER
:
2093 case MATCHCRITERIA_AGE_GREATER_HOURS
:
2094 case MATCHCRITERIA_AGE_LOWER_HOURS
:
2095 case MATCHCRITERIA_SCORE_GREATER
:
2096 case MATCHCRITERIA_SCORE_LOWER
:
2097 case MATCHCRITERIA_SCORE_EQUAL
:
2098 case MATCHCRITERIA_SIZE_GREATER
:
2099 case MATCHCRITERIA_SIZE_SMALLER
:
2100 case MATCHCRITERIA_SIZE_EQUAL
:
2101 case MATCHCRITERIA_COLORLABEL
:
2102 case MATCHCRITERIA_NOT_COLORLABEL
:
2103 return g_strdup_printf("%s %i", criteria_str
, matcher
->value
);
2104 case MATCHCRITERIA_ALL
:
2105 case MATCHCRITERIA_UNREAD
:
2106 case MATCHCRITERIA_NOT_UNREAD
:
2107 case MATCHCRITERIA_NEW
:
2108 case MATCHCRITERIA_NOT_NEW
:
2109 case MATCHCRITERIA_MARKED
:
2110 case MATCHCRITERIA_NOT_MARKED
:
2111 case MATCHCRITERIA_DELETED
:
2112 case MATCHCRITERIA_NOT_DELETED
:
2113 case MATCHCRITERIA_REPLIED
:
2114 case MATCHCRITERIA_NOT_REPLIED
:
2115 case MATCHCRITERIA_FORWARDED
:
2116 case MATCHCRITERIA_NOT_FORWARDED
:
2117 case MATCHCRITERIA_LOCKED
:
2118 case MATCHCRITERIA_NOT_LOCKED
:
2119 case MATCHCRITERIA_SPAM
:
2120 case MATCHCRITERIA_NOT_SPAM
:
2121 case MATCHCRITERIA_HAS_ATTACHMENT
:
2122 case MATCHCRITERIA_HAS_NO_ATTACHMENT
:
2123 case MATCHCRITERIA_SIGNED
:
2124 case MATCHCRITERIA_NOT_SIGNED
:
2125 case MATCHCRITERIA_PARTIAL
:
2126 case MATCHCRITERIA_NOT_PARTIAL
:
2127 case MATCHCRITERIA_IGNORE_THREAD
:
2128 case MATCHCRITERIA_NOT_IGNORE_THREAD
:
2129 case MATCHCRITERIA_WATCH_THREAD
:
2130 case MATCHCRITERIA_NOT_WATCH_THREAD
:
2131 case MATCHCRITERIA_TAGGED
:
2132 case MATCHCRITERIA_NOT_TAGGED
:
2133 return g_strdup(criteria_str
);
2134 case MATCHCRITERIA_TEST
:
2135 case MATCHCRITERIA_NOT_TEST
:
2136 case MATCHCRITERIA_DATE_AFTER
:
2137 case MATCHCRITERIA_DATE_BEFORE
:
2138 quoted_expr
= matcher_quote_str(matcher
->expr
);
2139 matcher_str
= g_strdup_printf("%s \"%s\"",
2140 criteria_str
, quoted_expr
);
2141 g_free(quoted_expr
);
2143 case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK
:
2144 case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK
:
2145 quoted_header
= matcher_quote_str(matcher
->header
);
2146 quoted_expr
= matcher_quote_str(matcher
->expr
);
2147 matcher_str
= g_strdup_printf("%s \"%s\" in \"%s\"",
2148 criteria_str
, quoted_header
, quoted_expr
);
2149 g_free(quoted_header
);
2150 g_free(quoted_expr
);
2154 matchtype_str
= NULL
;
2155 for (i
= 0; i
< sizeof matchparser_tab
/ sizeof matchparser_tab
[0]; i
++) {
2156 if (matchparser_tab
[i
].id
== matcher
->matchtype
)
2157 matchtype_str
= matchparser_tab
[i
].str
;
2160 if (matchtype_str
== NULL
)
2163 switch (matcher
->matchtype
) {
2164 case MATCHTYPE_MATCH
:
2165 case MATCHTYPE_MATCHCASE
:
2166 case MATCHTYPE_REGEXP
:
2167 case MATCHTYPE_REGEXPCASE
:
2168 quoted_expr
= matcher_quote_str(matcher
->expr
);
2169 if (matcher
->header
) {
2170 quoted_header
= matcher_quote_str(matcher
->header
);
2171 matcher_str
= g_strdup_printf
2172 ("%s \"%s\" %s \"%s\"",
2173 criteria_str
, quoted_header
,
2174 matchtype_str
, quoted_expr
);
2175 g_free(quoted_header
);
2178 matcher_str
= g_strdup_printf
2179 ("%s %s \"%s\"", criteria_str
,
2180 matchtype_str
, quoted_expr
);
2181 g_free(quoted_expr
);
2189 *\brief Convert a list of conditions to a string
2191 *\param matchers List of conditions
2193 *\return gchar * Newly allocated string
2195 gchar
*matcherlist_to_string(const MatcherList
*matchers
)
2201 gchar
*result
= NULL
;
2203 count
= g_slist_length(matchers
->matchers
);
2204 vstr
= g_new(gchar
*, count
+ 1);
2206 for (l
= matchers
->matchers
, cur_str
= vstr
; l
!= NULL
;
2207 l
= g_slist_next(l
), cur_str
++) {
2208 *cur_str
= matcherprop_to_string((MatcherProp
*) l
->data
);
2209 if (*cur_str
== NULL
)
2214 if (matchers
->bool_and
)
2215 result
= g_strjoinv(" & ", vstr
);
2217 result
= g_strjoinv(" | ", vstr
);
2219 for (cur_str
= vstr
; *cur_str
!= NULL
; cur_str
++)
2227 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
2228 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
2230 static void add_str_default(gchar
** dest
,
2231 const gchar
* s
, const gchar
* d
)
2233 gchar quoted_str
[4096];
2241 quote_cmd_argument(quoted_str
, sizeof(quoted_str
), str
);
2242 strcpy(* dest
, quoted_str
);
2244 (* dest
) += strlen(* dest
);
2247 /* matching_build_command() - preferably cmd should be unescaped */
2249 *\brief Build the command-line to execute
2251 *\param cmd String with command-line specifiers
2252 *\param info Message info to use for command
2254 *\return gchar * Newly allocated string
2256 gchar
*matching_build_command(const gchar
*cmd
, MsgInfo
*info
)
2258 const gchar
*s
= cmd
;
2259 gchar
*filename
= NULL
;
2260 gchar
*processed_cmd
;
2264 const gchar
*const no_subject
= _("(none)") ;
2265 const gchar
*const no_from
= _("(none)") ;
2266 const gchar
*const no_to
= _("(none)") ;
2267 const gchar
*const no_cc
= _("(none)") ;
2268 const gchar
*const no_date
= _("(none)") ;
2269 const gchar
*const no_msgid
= _("(none)") ;
2270 const gchar
*const no_newsgroups
= _("(none)") ;
2271 const gchar
*const no_references
= _("(none)") ;
2273 size
= STRLEN_ZERO(cmd
) + 1;
2274 while (*s
!= '\0') {
2281 case 's': /* subject */
2282 size
+= STRLEN_DEFAULT(info
->subject
, no_subject
) - 2;
2284 case 'f': /* from */
2285 size
+= STRLEN_DEFAULT(info
->from
, no_from
) - 2;
2288 size
+= STRLEN_DEFAULT(info
->to
, no_to
) - 2;
2291 size
+= STRLEN_DEFAULT(info
->cc
, no_cc
) - 2;
2293 case 'd': /* date */
2294 size
+= STRLEN_DEFAULT(info
->date
, no_date
) - 2;
2296 case 'i': /* message-id */
2297 size
+= STRLEN_DEFAULT(info
->msgid
, no_msgid
) - 2;
2299 case 'n': /* newsgroups */
2300 size
+= STRLEN_DEFAULT(info
->newsgroups
, no_newsgroups
) - 2;
2302 case 'r': /* references */
2303 /* FIXME: using the inreplyto header for reference */
2304 size
+= STRLEN_DEFAULT(info
->inreplyto
, no_references
) - 2;
2306 case 'F': /* file */
2307 if (filename
== NULL
)
2308 filename
= folder_item_fetch_msg(info
->folder
, info
->msgnum
);
2310 if (filename
== NULL
) {
2311 g_warning("filename is not set");
2315 size
+= strlen(filename
) - 2;
2324 /* as the string can be quoted, we double the result */
2327 processed_cmd
= g_new0(gchar
, size
);
2331 while (*s
!= '\0') {
2339 case 's': /* subject */
2340 add_str_default(&p
, info
->subject
,
2343 case 'f': /* from */
2344 add_str_default(&p
, info
->from
,
2348 add_str_default(&p
, info
->to
,
2352 add_str_default(&p
, info
->cc
,
2355 case 'd': /* date */
2356 add_str_default(&p
, info
->date
,
2359 case 'i': /* message-id */
2360 add_str_default(&p
, info
->msgid
,
2363 case 'n': /* newsgroups */
2364 add_str_default(&p
, info
->newsgroups
,
2367 case 'r': /* references */
2368 /* FIXME: using the inreplyto header for references */
2369 add_str_default(&p
, info
->inreplyto
, no_references
);
2371 case 'F': /* file */
2372 if (filename
!= NULL
)
2373 add_str_default(&p
, filename
, NULL
);
2392 return processed_cmd
;
2394 #undef STRLEN_DEFAULT
2397 /* ************************************************************ */
2401 *\brief Write filtering list to file
2404 *\param prefs_filtering List of filtering conditions
2406 static int prefs_filtering_write(FILE *fp
, GSList
*prefs_filtering
)
2410 for (cur
= prefs_filtering
; cur
!= NULL
; cur
= cur
->next
) {
2411 gchar
*filtering_str
= NULL
;
2412 gchar
*tmp_name
= NULL
;
2413 FilteringProp
*prop
= NULL
;
2415 if (NULL
== (prop
= (FilteringProp
*) cur
->data
))
2418 if (NULL
== (filtering_str
= filteringprop_to_string(prop
)))
2421 if (prop
->enabled
) {
2422 if (claws_fputs("enabled ", fp
) == EOF
) {
2423 FILE_OP_ERROR("filtering config", "claws_fputs");
2424 g_free(filtering_str
);
2428 if (claws_fputs("disabled ", fp
) == EOF
) {
2429 FILE_OP_ERROR("filtering config", "claws_fputs");
2430 g_free(filtering_str
);
2435 if (claws_fputs("rulename \"", fp
) == EOF
) {
2436 FILE_OP_ERROR("filtering config", "claws_fputs");
2437 g_free(filtering_str
);
2440 tmp_name
= prop
->name
;
2441 while (tmp_name
&& *tmp_name
!= '\0') {
2442 if (*tmp_name
!= '"') {
2443 if (claws_fputc(*tmp_name
, fp
) == EOF
) {
2444 FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2445 g_free(filtering_str
);
2448 } else if (*tmp_name
== '"') {
2449 if (claws_fputc('\\', fp
) == EOF
||
2450 claws_fputc('"', fp
) == EOF
) {
2451 FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2452 g_free(filtering_str
);
2458 if (claws_fputs("\" ", fp
) == EOF
) {
2459 FILE_OP_ERROR("filtering config", "claws_fputs");
2460 g_free(filtering_str
);
2464 if (prop
->account_id
!= 0) {
2467 tmp
= g_strdup_printf("account %d ", prop
->account_id
);
2468 if (claws_fputs(tmp
, fp
) == EOF
) {
2469 FILE_OP_ERROR("filtering config", "claws_fputs");
2470 g_free(filtering_str
);
2477 if(claws_fputs(filtering_str
, fp
) == EOF
||
2478 claws_fputc('\n', fp
) == EOF
) {
2479 FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2480 g_free(filtering_str
);
2483 g_free(filtering_str
);
2489 typedef struct _NodeLoopData
{
2495 *\brief Write matchers from a folder item
2497 *\param node Node with folder info
2498 *\param data File pointer
2500 *\return gboolean FALSE
2502 static gboolean
prefs_matcher_write_func(GNode
*node
, gpointer d
)
2505 NodeLoopData
*data
= (NodeLoopData
*)d
;
2507 GSList
*prefs_filtering
;
2510 /* prevent warning */
2511 if (item
->path
== NULL
)
2513 id
= folder_item_get_identifier(item
);
2516 prefs_filtering
= item
->prefs
->processing
;
2518 if (prefs_filtering
!= NULL
) {
2519 if (fprintf(data
->fp
, "[%s]\n", id
) < 0) {
2523 if (prefs_filtering_write(data
->fp
, prefs_filtering
) < 0) {
2527 if (claws_fputc('\n', data
->fp
) == EOF
) {
2539 *\brief Save matchers from folder items
2543 static int prefs_matcher_save(FILE *fp
)
2551 for (cur
= folder_get_list() ; cur
!= NULL
; cur
= g_list_next(cur
)) {
2554 folder
= (Folder
*) cur
->data
;
2555 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
2556 prefs_matcher_write_func
, &data
);
2559 if (data
.error
== TRUE
)
2562 /* pre global rules */
2563 if (fprintf(fp
, "[preglobal]\n") < 0 ||
2564 prefs_filtering_write(fp
, pre_global_processing
) < 0 ||
2565 claws_fputc('\n', fp
) == EOF
)
2568 /* post global rules */
2569 if (fprintf(fp
, "[postglobal]\n") < 0 ||
2570 prefs_filtering_write(fp
, post_global_processing
) < 0 ||
2571 claws_fputc('\n', fp
) == EOF
)
2574 /* filtering rules */
2575 if (fprintf(fp
, "[filtering]\n") < 0 ||
2576 prefs_filtering_write(fp
, filtering_rules
) < 0 ||
2577 claws_fputc('\n', fp
) == EOF
)
2584 *\brief Write filtering / matcher configuration file
2586 void prefs_matcher_write_config(void)
2591 debug_print("Writing matcher configuration...\n");
2593 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
2596 if ((pfile
= prefs_write_open(rcpath
)) == NULL
) {
2597 g_warning("failed to write configuration to file");
2604 if (prefs_matcher_save(pfile
->fp
) < 0) {
2605 g_warning("failed to write configuration to file");
2606 prefs_file_close_revert(pfile
);
2607 } else if (prefs_file_close(pfile
) < 0) {
2608 g_warning("failed to save configuration to file");
2613 *\brief Read matcher configuration
2615 void prefs_matcher_read_config(void)
2620 create_matchparser_hashtab();
2621 prefs_filtering_clear();
2623 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, MATCHER_RC
, NULL
);
2625 f
= claws_fopen(rcpath
, "rb");
2629 matcher_parser_start_parsing(f
);
2630 claws_fclose(matcher_parserin
);