Added support for local image attachments to the Litehtml plugin
[claws.git] / src / matcher.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002-2014 by the Claws Mail Team and Hiroyuki Yamamoto
4  *
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.
9  *
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.
14  *
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/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <errno.h>
31
32 #ifdef USE_PTHREAD
33 #include <pthread.h>
34 #endif
35
36 #include "defs.h"
37 #include "utils.h"
38 #include "procheader.h"
39 #include "matcher.h"
40 #include "matcher_parser.h"
41 #include "prefs_gtk.h"
42 #include "addr_compl.h"
43 #include "codeconv.h"
44 #include "quoted-printable.h"
45 #include "claws.h"
46 #include <ctype.h>
47 #include "prefs_common.h"
48 #include "log.h"
49 #include "tags.h"
50 #include "folder_item_prefs.h"
51 #include "procmsg.h"
52 #include "file-utils.h"
53
54 /*!
55  *\brief        Keyword lookup element
56  */
57 struct _MatchParser {
58         gint id;                /*!< keyword id */ 
59         gchar *str;             /*!< keyword */
60 };
61 typedef struct _MatchParser MatchParser;
62
63 /*!
64  *\brief        Table with strings and ids used by the lexer and
65  *              the parser. New keywords can be added here.
66  */
67 static const MatchParser matchparser_tab[] = {
68         /* msginfo flags */
69         {MATCHCRITERIA_ALL, "all"},
70         {MATCHCRITERIA_UNREAD, "unread"},
71         {MATCHCRITERIA_NOT_UNREAD, "~unread"},
72         {MATCHCRITERIA_NEW, "new"},
73         {MATCHCRITERIA_NOT_NEW, "~new"},
74         {MATCHCRITERIA_MARKED, "marked"},
75         {MATCHCRITERIA_NOT_MARKED, "~marked"},
76         {MATCHCRITERIA_DELETED, "deleted"},
77         {MATCHCRITERIA_NOT_DELETED, "~deleted"},
78         {MATCHCRITERIA_REPLIED, "replied"},
79         {MATCHCRITERIA_NOT_REPLIED, "~replied"},
80         {MATCHCRITERIA_FORWARDED, "forwarded"},
81         {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
82         {MATCHCRITERIA_LOCKED, "locked"},
83         {MATCHCRITERIA_NOT_LOCKED, "~locked"},
84         {MATCHCRITERIA_COLORLABEL, "colorlabel"},
85         {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
86         {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
87         {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
88         {MATCHCRITERIA_WATCH_THREAD, "watch_thread"},
89         {MATCHCRITERIA_NOT_WATCH_THREAD, "~watch_thread"},
90         {MATCHCRITERIA_SPAM, "spam"},
91         {MATCHCRITERIA_NOT_SPAM, "~spam"},
92         {MATCHCRITERIA_HAS_ATTACHMENT, "has_attachment"},
93         {MATCHCRITERIA_HAS_NO_ATTACHMENT, "~has_attachment"},
94         {MATCHCRITERIA_SIGNED, "signed"},
95         {MATCHCRITERIA_NOT_SIGNED, "~signed"},
96
97         /* msginfo headers */
98         {MATCHCRITERIA_SUBJECT, "subject"},
99         {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
100         {MATCHCRITERIA_FROM, "from"},
101         {MATCHCRITERIA_NOT_FROM, "~from"},
102         {MATCHCRITERIA_TO, "to"},
103         {MATCHCRITERIA_NOT_TO, "~to"},
104         {MATCHCRITERIA_CC, "cc"},
105         {MATCHCRITERIA_NOT_CC, "~cc"},
106         {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
107         {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
108         {MATCHCRITERIA_TAG, "tag"},
109         {MATCHCRITERIA_NOT_TAG, "~tag"},
110         {MATCHCRITERIA_TAGGED, "tagged"},
111         {MATCHCRITERIA_NOT_TAGGED, "~tagged"},
112         {MATCHCRITERIA_AGE_GREATER, "age_greater"},
113         {MATCHCRITERIA_AGE_LOWER, "age_lower"},
114         {MATCHCRITERIA_AGE_GREATER_HOURS, "age_greater_hours"},
115         {MATCHCRITERIA_AGE_LOWER_HOURS, "age_lower_hours"},
116         {MATCHCRITERIA_DATE_AFTER, "date_after"},
117         {MATCHCRITERIA_DATE_BEFORE, "date_before"},
118         {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
119         {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
120         {MATCHCRITERIA_MESSAGEID, "messageid"},
121         {MATCHCRITERIA_NOT_MESSAGEID, "~messageid"},
122         {MATCHCRITERIA_INREPLYTO, "inreplyto"},
123         {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
124         {MATCHCRITERIA_REFERENCES, "references"},
125         {MATCHCRITERIA_NOT_REFERENCES, "~references"},
126         {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
127         {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
128         {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
129         {MATCHCRITERIA_PARTIAL, "partial"},
130         {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
131         {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
132         {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
133
134         {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
135         {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
136         {MATCHCRITERIA_SIZE_EQUAL,   "size_equal"},
137
138         /* content have to be read */
139         {MATCHCRITERIA_HEADER, "header"},
140         {MATCHCRITERIA_NOT_HEADER, "~header"},
141         {MATCHCRITERIA_HEADERS_PART, "headers_part"},
142         {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
143         {MATCHCRITERIA_HEADERS_CONT, "headers_cont"},
144         {MATCHCRITERIA_NOT_HEADERS_CONT, "~headers_cont"},
145         {MATCHCRITERIA_MESSAGE, "message"},
146         {MATCHCRITERIA_NOT_MESSAGE, "~message"},
147         {MATCHCRITERIA_BODY_PART, "body_part"},
148         {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
149         {MATCHCRITERIA_TEST, "test"},
150         {MATCHCRITERIA_NOT_TEST, "~test"},
151
152         /* match type */
153         {MATCHTYPE_MATCHCASE, "matchcase"},
154         {MATCHTYPE_MATCH, "match"},
155         {MATCHTYPE_REGEXPCASE, "regexpcase"},
156         {MATCHTYPE_REGEXP, "regexp"},
157
158         /* actions */
159         {MATCHACTION_SCORE, "score"},    /* for backward compatibility */
160         {MATCHACTION_MOVE, "move"},
161         {MATCHACTION_COPY, "copy"},
162         {MATCHACTION_DELETE, "delete"},
163         {MATCHACTION_MARK, "mark"},
164         {MATCHACTION_UNMARK, "unmark"},
165         {MATCHACTION_LOCK, "lock"},
166         {MATCHACTION_UNLOCK, "unlock"},
167         {MATCHACTION_MARK_AS_READ, "mark_as_read"},
168         {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
169         {MATCHACTION_MARK_AS_SPAM, "mark_as_spam"},
170         {MATCHACTION_MARK_AS_HAM, "mark_as_ham"},
171         {MATCHACTION_FORWARD, "forward"},
172         {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
173         {MATCHACTION_EXECUTE, "execute"},
174         {MATCHACTION_COLOR, "color"},
175         {MATCHACTION_REDIRECT, "redirect"},
176         {MATCHACTION_CHANGE_SCORE, "change_score"},
177         {MATCHACTION_SET_SCORE, "set_score"},
178         {MATCHACTION_STOP, "stop"},
179         {MATCHACTION_HIDE, "hide"},
180         {MATCHACTION_IGNORE, "ignore"},
181         {MATCHACTION_WATCH, "watch"},
182         {MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"},
183         {MATCHACTION_SET_TAG, "set_tag"},
184         {MATCHACTION_UNSET_TAG, "unset_tag"},
185         {MATCHACTION_CLEAR_TAGS, "clear_tags"},
186 };
187
188 enum {
189         MATCH_ANY = 0,
190         MATCH_ALL = 1,
191         MATCH_ONE = 2
192 };
193
194 enum {
195         CONTEXT_SUBJECT,
196         CONTEXT_FROM,
197         CONTEXT_TO,
198         CONTEXT_CC,
199         CONTEXT_NEWSGROUPS,
200         CONTEXT_MESSAGEID,
201         CONTEXT_IN_REPLY_TO,
202         CONTEXT_REFERENCES,
203         CONTEXT_HEADER,
204         CONTEXT_HEADER_LINE,
205         CONTEXT_BODY_LINE,
206         CONTEXT_TAG,
207         N_CONTEXT_STRS
208 };
209
210 static gchar *context_str[N_CONTEXT_STRS];
211
212 void matcher_init(void)
213 {
214         if (context_str[CONTEXT_SUBJECT] != NULL)
215                 return;
216
217         context_str[CONTEXT_SUBJECT] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Subject:"));
218         context_str[CONTEXT_FROM] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
219         context_str[CONTEXT_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
220         context_str[CONTEXT_CC] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
221         context_str[CONTEXT_NEWSGROUPS] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Newsgroups:"));
222         context_str[CONTEXT_MESSAGEID] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Message-ID:"));
223         context_str[CONTEXT_IN_REPLY_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("In-Reply-To:"));
224         context_str[CONTEXT_REFERENCES] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("References:"));
225         context_str[CONTEXT_HEADER] = g_strdup(_("header"));
226         context_str[CONTEXT_HEADER_LINE] = g_strdup(_("header line"));
227         context_str[CONTEXT_BODY_LINE] = g_strdup(_("body line"));
228         context_str[CONTEXT_TAG]  = g_strdup(_("tag"));
229 }
230
231 void matcher_done(void)
232 {
233         int i;
234         for (i = 0; i < N_CONTEXT_STRS; i++) {
235                 g_free(context_str[i]);
236                 context_str[i] = NULL;
237         }
238 }
239
240 extern gboolean debug_filtering_session;
241
242 /*!
243  *\brief        Look up table with keywords defined in \sa matchparser_tab
244  */
245 static GHashTable *matchparser_hashtab;
246
247 /*!
248  *\brief        Translate keyword id to keyword string
249  *
250  *\param        id Id of keyword
251  *
252  *\return       const gchar * Keyword
253  */
254 const gchar *get_matchparser_tab_str(gint id)
255 {
256         gint i;
257
258         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
259                 if (matchparser_tab[i].id == id)
260                         return matchparser_tab[i].str;
261         }
262         return NULL;
263 }
264
265 /*!
266  *\brief        Create keyword lookup table
267  */
268 static void create_matchparser_hashtab(void)
269 {
270         int i;
271         
272         if (matchparser_hashtab) return;
273         matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
274         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
275                 g_hash_table_insert(matchparser_hashtab,
276                                     matchparser_tab[i].str,
277                                     (gpointer) &matchparser_tab[i]);
278 }
279
280 /*!
281  *\brief        Return a keyword id from a keyword string
282  *
283  *\param        str Keyword string
284  *
285  *\return       gint Keyword id
286  */
287 gint get_matchparser_tab_id(const gchar *str)
288 {
289         MatchParser *res;
290
291         if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
292                 return res->id;
293         } else
294                 return -1;
295 }
296
297 /* **************** data structure allocation **************** */
298
299 /*!
300  *\brief        Allocate a structure for a filtering / scoring
301  *              "condition" (a matcher structure)
302  *
303  *\param        criteria Criteria ID (MATCHCRITERIA_XXXX)
304  *\param        header Header string (if criteria is MATCHCRITERIA_HEADER
305                         or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
306  *\param        matchtype Type of action (MATCHTYPE_XXX)
307  *\param        expr String value or expression to check
308  *\param        value Integer value to check
309  *
310  *\return       MatcherProp * Pointer to newly allocated structure
311  */
312 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
313                               gint matchtype, const gchar *expr,
314                               int value)
315 {
316         MatcherProp *prop;
317
318         prop = g_new0(MatcherProp, 1);
319         prop->criteria = criteria;
320         prop->header = header != NULL ? g_strdup(header) : NULL;
321
322         prop->expr = expr != NULL ? g_strdup(expr) : NULL;
323
324         prop->matchtype = matchtype;
325 #ifndef G_OS_WIN32
326         prop->preg = NULL;
327 #endif
328         prop->casefold_expr = NULL;
329         prop->value = value;
330         prop->error = 0;
331
332         return prop;
333 }
334
335 /*!
336  *\brief        Free a matcher structure
337  *
338  *\param        prop Pointer to matcher structure allocated with
339  *              #matcherprop_new
340  */
341 void matcherprop_free(MatcherProp *prop)
342 {
343         g_free(prop->expr);
344         g_free(prop->header);
345 #ifndef G_OS_WIN32
346         if (prop->preg != NULL) {
347                 regfree(prop->preg);
348                 g_free(prop->preg);
349         }
350         g_free(prop->casefold_expr);
351 #endif
352         g_free(prop);
353 }
354
355 /*!
356  *\brief        Copy a matcher structure
357  *
358  *\param        src Matcher structure to copy
359  *
360  *\return       MatcherProp * Pointer to newly allocated matcher structure
361  */
362 MatcherProp *matcherprop_copy(const MatcherProp *src)
363 {
364         MatcherProp *prop = g_new0(MatcherProp, 1);
365         
366         prop->criteria = src->criteria;
367         prop->header = src->header ? g_strdup(src->header) : NULL;
368         prop->expr = src->expr ? g_strdup(src->expr) : NULL;
369         prop->matchtype = src->matchtype;
370         
371 #ifndef G_OS_WIN32
372         prop->preg = NULL; /* will be re-evaluated */
373 #endif
374         prop->casefold_expr = src->casefold_expr ? g_strdup(src->casefold_expr) : NULL;
375         prop->value = src->value;
376         prop->error = src->error;       
377         return prop;            
378 }
379
380 /* ************** match ******************************/
381
382 static gboolean match_with_addresses_in_addressbook
383         (MatcherProp *prop, GSList *address_list, gint type,
384          gchar* folderpath, gint match)
385 {
386         GSList *walk = NULL;
387         gboolean found = FALSE;
388         gchar *path = NULL;
389
390         cm_return_val_if_fail(address_list != NULL, FALSE);
391
392         debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
393                                 g_slist_length(address_list), folderpath?folderpath:"(null)");
394
395         if (folderpath == NULL ||
396                 strcasecmp(folderpath, "Any") == 0 ||
397                 *folderpath == '\0')
398                 path = NULL;
399         else
400                 path = folderpath;
401         
402         start_address_completion(path);
403
404         for (walk = address_list; walk != NULL; walk = walk->next) {
405                 /* exact matching of email address */
406                 guint num_addr = complete_address(walk->data);
407                 found = FALSE;
408                 if (num_addr > 1) {
409                         /* skip first item (this is the search string itself) */
410                         int i = 1;
411                         for (; i < num_addr && !found; i++) {
412                                 gchar *addr = get_complete_address(i);
413                                 extract_address(addr);
414                                 if (strcasecmp(addr, walk->data) == 0) {
415                                         found = TRUE;
416
417                                         /* debug output */
418                                         if (debug_filtering_session
419                                                         && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
420                                                 log_print(LOG_DEBUG_FILTERING,
421                                                                 "address [ %s ] matches\n",
422                                                                 (gchar *)walk->data);
423                                         }
424                                 }
425                                 g_free(addr);
426                         }
427                 }
428                 /* debug output */
429                 if (debug_filtering_session
430                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH
431                                 && !found) {
432                         log_print(LOG_DEBUG_FILTERING,
433                                         "address [ %s ] does NOT match\n",
434                                         (gchar *)walk->data);
435                 }
436                 g_free(walk->data);
437
438                 if (match == MATCH_ALL) {
439                         /* if matching all addresses, stop if one doesn't match */
440                         if (!found) {
441                                 /* debug output */
442                                 if (debug_filtering_session
443                                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
444                                         log_print(LOG_DEBUG_FILTERING,
445                                                         "not all address match (matching all)\n");
446                                 }
447                                 break;
448                         }
449                 } else if (match == MATCH_ANY) {
450                         /* if matching any address, stop if one does match */
451                         if (found) {
452                                 /* debug output */
453                                 if (debug_filtering_session
454                                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
455                                         log_print(LOG_DEBUG_FILTERING,
456                                                         "at least one address matches (matching any)\n");
457                                 }
458                                 break;
459                         }
460                 }
461                 /* MATCH_ONE: there should be only one loop iteration */
462         }
463
464         end_address_completion();
465         
466         return found;
467 }
468
469 /*!
470  *\brief        Find out if a string matches a condition
471  *
472  *\param        prop Matcher structure
473  *\param        str String to check 
474  *
475  *\return       gboolean TRUE if str matches the condition in the 
476  *              matcher structure
477  */
478 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str,
479                                          const gchar *debug_context)
480 {
481         gchar *str1;
482         const gchar *down_expr;
483         gboolean ret = FALSE;
484         gboolean should_free = FALSE;
485         if (str == NULL)
486                 return FALSE;
487
488         if (prop->matchtype == MATCHTYPE_REGEXPCASE ||
489             prop->matchtype == MATCHTYPE_MATCHCASE) {
490                 str1 = g_utf8_casefold(str, -1);
491                 if (!prop->casefold_expr) {
492                         prop->casefold_expr = g_utf8_casefold(prop->expr, -1);
493                 }
494                 down_expr = prop->casefold_expr;
495
496                 should_free = TRUE;
497         } else {
498                 str1 = (gchar *)str;
499                 down_expr = (gchar *)prop->expr;
500                 should_free = FALSE;
501         }
502
503         switch (prop->matchtype) {
504         case MATCHTYPE_REGEXPCASE:
505         case MATCHTYPE_REGEXP:
506                 if (!prop->preg && (prop->error == 0)) {
507                         prop->preg = g_new0(regex_t, 1);
508                         /* if regexp then don't use the escaped string */
509                         if (regcomp(prop->preg, down_expr,
510                                     REG_NOSUB | REG_EXTENDED
511                                     | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
512                                     ? REG_ICASE : 0)) != 0) {
513                                 prop->error = 1;
514                                 g_free(prop->preg);
515                                 prop->preg = NULL;
516                         }
517                 }
518                 if (prop->preg == NULL) {
519                         ret = FALSE;
520                         goto free_strs;
521                 }
522                 
523                 if (regexec(prop->preg, str1, 0, NULL, 0) == 0)
524                         ret = TRUE;
525                 else
526                         ret = FALSE;
527
528                 /* debug output */
529                 if (debug_filtering_session
530                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
531                         gchar *stripped = g_strdup(str);
532
533                         strretchomp(stripped);
534                         if (ret) {
535                                 log_print(LOG_DEBUG_FILTERING,
536                                                 "%s value [ %s ] matches regular expression [ %s ] (%s)\n",
537                                                 debug_context, stripped, prop->expr,
538                                                 prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
539                         } else {
540                                 log_print(LOG_DEBUG_FILTERING,
541                                                 "%s value [ %s ] does NOT match regular expression [ %s ] (%s)\n",
542                                                 debug_context, stripped, prop->expr,
543                                                 prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
544                         }
545                         g_free(stripped);
546                 }
547                 break;
548         case MATCHTYPE_MATCHCASE:
549         case MATCHTYPE_MATCH:
550                 ret = (strstr(str1, down_expr) != NULL);
551
552                 /* debug output */
553                 if (debug_filtering_session
554                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
555                         gchar *stripped = g_strdup(str);
556
557                         strretchomp(stripped);
558                         if (ret) {
559                                 log_print(LOG_DEBUG_FILTERING,
560                                                 "%s value [ %s ] contains [ %s ] (%s)\n",
561                                                 debug_context, stripped, prop->expr,
562                                                 prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
563                         } else {
564                                 log_print(LOG_DEBUG_FILTERING,
565                                                 "%s value [ %s ] does NOT contain [ %s ] (%s)\n",
566                                                 debug_context, stripped, prop->expr,
567                                                 prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
568                         }
569                         g_free(stripped);
570                 }
571                 break;
572
573         default:
574                 break;
575         }
576         
577 free_strs:
578         if (should_free) {
579                 g_free(str1);
580         }
581         return ret;
582 }
583
584 /*!
585  *\brief        Find out if a tag matches a condition
586  *
587  *\param        prop Matcher structure
588  *\param        msginfo message to check
589  *
590  *\return       gboolean TRUE if msginfo matches the condition in the 
591  *              matcher structure
592  */
593 static gboolean matcherprop_tag_match(MatcherProp *prop, MsgInfo *msginfo,
594                                          const gchar *debug_context)
595 {
596         gboolean ret = FALSE;
597         GSList *cur;
598
599         if (msginfo == NULL || msginfo->tags == NULL)
600                 return FALSE;
601
602         for (cur = msginfo->tags; cur; cur = cur->next) {
603                 const gchar *str = tags_get_tag(GPOINTER_TO_INT(cur->data));
604                 if (!str)
605                         continue;
606                 if (matcherprop_string_match(prop, str, debug_context)) {
607                         ret = TRUE;
608                         break;
609                 }
610         }
611         return ret;
612 }
613
614 /*!
615  *\brief        Find out if the string-ed list matches a condition
616  *
617  *\param        prop Matcher structure
618  *\param        list GSList of strings to check
619  *
620  *\return       gboolean TRUE if str matches the condition in the 
621  *              matcher structure
622  */
623 static gboolean matcherprop_list_match(MatcherProp *prop, const GSList *list,
624 const gchar *debug_context)
625 {
626         const GSList *cur;
627
628         for(cur = list; cur != NULL; cur = cur->next) {
629                 if (matcherprop_string_match(prop, (gchar *)cur->data, debug_context))
630                         return TRUE;
631         }
632         return FALSE;
633 }
634
635 static gboolean matcherprop_header_line_match(MatcherProp *prop, const gchar *hdr,
636                                          const gchar *str, const gboolean both,
637                                          const gchar *debug_context)
638 {
639         gboolean res = FALSE;
640
641         if (hdr == NULL || str == NULL)
642                 return FALSE;
643
644         if (both) {
645                 /* Search in all header names and content.
646                  */
647                 gchar *line = g_strdup_printf("%s %s", hdr, str);
648                 res = matcherprop_string_match(prop, line, debug_context);
649                 g_free(line);
650         } else {
651                 /* Search only in content and exclude private headers.
652                  * E.g.: searching for "H foo" in folder x/foo would return
653                  * *all* mail as SCF and RMID will match.
654                  * Searching for "H sent" would return all resent messages
655                  * as "Resent-From: whatever" will match.
656                  */
657                 if (procheader_header_is_internal(hdr))
658                         return FALSE;
659                 res = matcherprop_string_match(prop, str, debug_context);
660         }
661
662         return res;
663 }
664
665 #ifdef USE_PTHREAD
666 typedef struct _thread_data {
667         const gchar *cmd;
668         gboolean done;
669 } thread_data;
670 #endif
671
672 #ifdef USE_PTHREAD
673 static void *matcher_test_thread(void *data)
674 {
675         thread_data *td = (thread_data *)data;
676         int result = -1;
677
678         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
679         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
680
681         result = system(td->cmd);
682         td->done = TRUE; /* let the caller thread join() */
683         return GINT_TO_POINTER(result);
684 }
685 #endif
686
687 /*!
688  *\brief        Execute a command defined in the matcher structure
689  *
690  *\param        prop Pointer to matcher structure
691  *\param        info Pointer to message info structure
692  *
693  *\return       gboolean TRUE if command was executed successfully
694  */
695 static gboolean matcherprop_match_test(const MatcherProp *prop, 
696                                           MsgInfo *info)
697 {
698         gchar *file;
699         gchar *cmd;
700         gint retval;
701 #ifdef USE_PTHREAD
702         pthread_t pt;
703         thread_data *td = g_new0(thread_data, 1);
704         void *res = NULL;
705         time_t start_time = time(NULL);
706 #endif
707
708         file = procmsg_get_message_file(info);
709         if (file == NULL) {
710 #ifdef USE_PTHREAD
711                 g_free(td);
712 #endif
713                 return FALSE;
714         }
715         g_free(file);           
716
717         cmd = matching_build_command(prop->expr, info);
718         if (cmd == NULL) {
719 #ifdef USE_PTHREAD
720                 g_free(td);
721 #endif  
722                 return FALSE;
723 }
724
725 #ifdef USE_PTHREAD
726         /* debug output */
727         if (debug_filtering_session
728                         && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
729                 log_print(LOG_DEBUG_FILTERING,
730                                 "starting threaded command [ %s ]\n",
731                                 cmd);
732         }
733
734         td->cmd = cmd;
735         td->done = FALSE;
736         if (pthread_create(&pt, NULL, matcher_test_thread, td) != 0)
737                 retval = system(cmd);
738         else {
739                 debug_print("waiting for test thread\n");
740                 while(!td->done) {
741                         /* don't let the interface freeze while waiting */
742                         if (time(NULL) - start_time > 0) {
743                                 claws_do_idle();
744                         }
745                         if (time(NULL) - start_time > 30) {
746                                 pthread_cancel(pt);
747                                 td->done = TRUE;
748                                 retval = -1;
749                         }
750                 }
751                 pthread_join(pt, &res);
752                 retval = GPOINTER_TO_INT(res);
753                 debug_print(" test thread returned %d\n", retval);
754         }
755         g_free(td);
756 #else
757         /* debug output */
758         if (debug_filtering_session
759                         && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
760                 log_print(LOG_DEBUG_FILTERING,
761                                 "starting synchronous command [ %s ]\n",
762                                 cmd);
763         }
764
765         retval = system(cmd);
766 #endif
767         debug_print("Command exit code: %d\n", retval);
768
769         /* debug output */
770         if (debug_filtering_session
771                         && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
772                 log_print(LOG_DEBUG_FILTERING,
773                                 "command returned [ %d ]\n",
774                                 retval);
775         }
776
777         g_free(cmd);
778         return (retval == 0);
779 }
780
781 /*!
782  *\brief        Check if a message matches the condition in a matcher
783  *              structure.
784  *
785  *\param        prop Pointer to matcher structure
786  *\param        info Pointer to message info
787  *
788  *\return       gboolean TRUE if a match
789  */
790 static gboolean matcherprop_match(MatcherProp *prop, 
791                                   MsgInfo *info)
792 {
793         time_t t;
794         gint age_mult_hours = 1;
795
796         switch(prop->criteria) {
797         case MATCHCRITERIA_ALL:
798                 return TRUE;
799         case MATCHCRITERIA_UNREAD:
800                 return MSG_IS_UNREAD(info->flags);
801         case MATCHCRITERIA_NOT_UNREAD:
802                 return !MSG_IS_UNREAD(info->flags);
803         case MATCHCRITERIA_NEW:
804                 return MSG_IS_NEW(info->flags);
805         case MATCHCRITERIA_NOT_NEW:
806                 return !MSG_IS_NEW(info->flags);
807         case MATCHCRITERIA_MARKED:
808                 return MSG_IS_MARKED(info->flags);
809         case MATCHCRITERIA_NOT_MARKED:
810                 return !MSG_IS_MARKED(info->flags);
811         case MATCHCRITERIA_DELETED:
812                 return MSG_IS_DELETED(info->flags);
813         case MATCHCRITERIA_NOT_DELETED:
814                 return !MSG_IS_DELETED(info->flags);
815         case MATCHCRITERIA_REPLIED:
816                 return MSG_IS_REPLIED(info->flags);
817         case MATCHCRITERIA_NOT_REPLIED:
818                 return !MSG_IS_REPLIED(info->flags);
819         case MATCHCRITERIA_FORWARDED:
820                 return MSG_IS_FORWARDED(info->flags);
821         case MATCHCRITERIA_NOT_FORWARDED:
822                 return !MSG_IS_FORWARDED(info->flags);
823         case MATCHCRITERIA_LOCKED:
824                 return MSG_IS_LOCKED(info->flags);
825         case MATCHCRITERIA_NOT_LOCKED:
826                 return !MSG_IS_LOCKED(info->flags);
827         case MATCHCRITERIA_SPAM:
828                 return MSG_IS_SPAM(info->flags);
829         case MATCHCRITERIA_NOT_SPAM:
830                 return !MSG_IS_SPAM(info->flags);
831         case MATCHCRITERIA_HAS_ATTACHMENT:
832                 return MSG_IS_WITH_ATTACHMENT(info->flags);
833         case MATCHCRITERIA_HAS_NO_ATTACHMENT:
834                 return !MSG_IS_WITH_ATTACHMENT(info->flags);
835         case MATCHCRITERIA_SIGNED:
836                 return MSG_IS_SIGNED(info->flags);
837         case MATCHCRITERIA_NOT_SIGNED:
838                 return !MSG_IS_SIGNED(info->flags);
839         case MATCHCRITERIA_COLORLABEL:
840         {
841                 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
842                 gboolean ret = (color == prop->value);
843
844                 /* debug output */
845                 if (debug_filtering_session
846                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
847                         if (ret) {
848                                 log_print(LOG_DEBUG_FILTERING,
849                                                 "message color value [ %d ] matches color value [ %d ]\n",
850                                                 color, prop->value);
851                         } else {
852                                 log_print(LOG_DEBUG_FILTERING,
853                                                 "message color value [ %d ] does NOT match color value [ %d ]\n",
854                                                 color, prop->value);
855                         }
856                 }
857                 return ret;
858         }
859         case MATCHCRITERIA_NOT_COLORLABEL:
860         {
861                 gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
862                 gboolean ret = (color != prop->value);
863
864                 /* debug output */
865                 if (debug_filtering_session
866                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
867                         if (ret) {
868                                 log_print(LOG_DEBUG_FILTERING,
869                                                 "message color value [ %d ] matches color value [ %d ]\n",
870                                                 color, prop->value);
871                         } else {
872                                 log_print(LOG_DEBUG_FILTERING,
873                                                 "message color value [ %d ] does NOT match color value [ %d ]\n",
874                                                 color, prop->value);
875                         }
876                 }
877                 return ret;
878         }
879         case MATCHCRITERIA_IGNORE_THREAD:
880                 return MSG_IS_IGNORE_THREAD(info->flags);
881         case MATCHCRITERIA_NOT_IGNORE_THREAD:
882                 return !MSG_IS_IGNORE_THREAD(info->flags);
883         case MATCHCRITERIA_WATCH_THREAD:
884                 return MSG_IS_WATCH_THREAD(info->flags);
885         case MATCHCRITERIA_NOT_WATCH_THREAD:
886                 return !MSG_IS_WATCH_THREAD(info->flags);
887         case MATCHCRITERIA_SUBJECT:
888                 return matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
889         case MATCHCRITERIA_NOT_SUBJECT:
890                 return !matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
891         case MATCHCRITERIA_FROM:
892                 return matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
893         case MATCHCRITERIA_NOT_FROM:
894                 return !matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
895         case MATCHCRITERIA_TO:
896                 return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
897         case MATCHCRITERIA_NOT_TO:
898                 return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
899         case MATCHCRITERIA_CC:
900                 return matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
901         case MATCHCRITERIA_NOT_CC:
902                 return !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
903         case MATCHCRITERIA_TO_OR_CC:
904                 return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
905                      || matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
906         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
907                 return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
908                      && !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
909         case MATCHCRITERIA_TAG:
910                 return matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
911         case MATCHCRITERIA_NOT_TAG:
912                 return !matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
913         case MATCHCRITERIA_TAGGED:
914                 return info->tags != NULL;
915         case MATCHCRITERIA_NOT_TAGGED:
916                 return info->tags == NULL;
917         case MATCHCRITERIA_AGE_GREATER:
918                 age_mult_hours = 24;
919                 /* Fallthrough intended */
920         case MATCHCRITERIA_AGE_GREATER_HOURS:
921         {
922                 gboolean ret;
923                 gint age;
924
925                 t = time(NULL);
926                 age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
927                 ret = (age >= prop->value);
928
929                 /* debug output */
930                 if (debug_filtering_session
931                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
932                         if (ret) {
933                                 log_print(LOG_DEBUG_FILTERING,
934                                                 "message age [ %d ] is greater than [ %d ]\n",
935                                                 age, prop->value);
936                         } else {
937                                 log_print(LOG_DEBUG_FILTERING,
938                                                 "message age [ %d ] is not greater than [ %d ]\n",
939                                                 age, prop->value);
940                         }
941                 }
942                 return ret;
943         }
944         case MATCHCRITERIA_DATE_AFTER:
945         {
946                 gboolean ret;
947
948                 ret = prop->value < info->date_t;
949
950                 /* debug output */
951                 if (debug_filtering_session
952                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
953                         if (ret) {
954                                 log_print(LOG_DEBUG_FILTERING,
955                                                 "message date [ %"G_GSIZE_FORMAT" ] is after [ %d ]\n",
956                                                 info->date_t, prop->value);
957                         } else {
958                                 log_print(LOG_DEBUG_FILTERING,
959                                                 "message date [ %ld ] is not after [ %d ]\n",
960                                                 info->date_t, prop->value);
961                         }
962                 }
963                 return ret;
964         }
965         case MATCHCRITERIA_AGE_LOWER:
966                 age_mult_hours = 24;
967                 /* Fallthrough intended */
968         case MATCHCRITERIA_AGE_LOWER_HOURS:
969         {
970                 gboolean ret;
971                 gint age;
972
973                 t = time(NULL);
974                 age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
975                 ret = (age < prop->value);
976
977                 /* debug output */
978                 if (debug_filtering_session
979                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
980                         if (ret) {
981                                 log_print(LOG_DEBUG_FILTERING,
982                                                 "message age [ %d ] is lower than [ %d ]\n",
983                                                 age, prop->value);
984                         } else {
985                                 log_print(LOG_DEBUG_FILTERING,
986                                                 "message age [ %d ] is not lower than [ %d ]\n",
987                                                 age, prop->value);
988                         }
989                 }
990                 return ret;
991         }
992         case MATCHCRITERIA_DATE_BEFORE:
993         {
994                 gboolean ret;
995
996                 ret = prop->value > info->date_t;
997
998                 /* debug output */
999                 if (debug_filtering_session
1000                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1001                         if (ret) {
1002                                 log_print(LOG_DEBUG_FILTERING,
1003                                                 "message date [ %ld ] is before [ %d ]\n",
1004                                                 info->date_t, prop->value);
1005                         } else {
1006                                 log_print(LOG_DEBUG_FILTERING,
1007                                                 "message date [ %ld ] is not before [ %d ]\n",
1008                                                 info->date_t, prop->value);
1009                         }
1010                 }
1011                 return ret;
1012         }
1013         case MATCHCRITERIA_SCORE_GREATER:
1014         {
1015                 gboolean ret = (info->score > prop->value);
1016
1017                 /* debug output */
1018                 if (debug_filtering_session
1019                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1020                         if (ret) {
1021                                 log_print(LOG_DEBUG_FILTERING,
1022                                                 "message score [ %d ] is greater than [ %d ]\n",
1023                                                 info->score, prop->value);
1024                         } else {
1025                                 log_print(LOG_DEBUG_FILTERING,
1026                                                 "message score [ %d ] is not greater than [ %d ]\n",
1027                                                 info->score, prop->value);
1028                         }
1029                 }
1030                 return ret;
1031         }
1032         case MATCHCRITERIA_SCORE_LOWER:
1033         {
1034                 gboolean ret = (info->score < prop->value);
1035
1036                 /* debug output */
1037                 if (debug_filtering_session
1038                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1039                         if (ret) {
1040                                 log_print(LOG_DEBUG_FILTERING,
1041                                                 "message score [ %d ] is lower than [ %d ]\n",
1042                                                 info->score, prop->value);
1043                         } else {
1044                                 log_print(LOG_DEBUG_FILTERING,
1045                                                 "message score [ %d ] is not lower than [ %d ]\n",
1046                                                 info->score, prop->value);
1047                         }
1048                 }
1049                 return ret;
1050         }
1051         case MATCHCRITERIA_SCORE_EQUAL:
1052         {
1053                 gboolean ret = (info->score == prop->value);
1054
1055                 /* debug output */
1056                 if (debug_filtering_session
1057                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1058                         if (ret) {
1059                                 log_print(LOG_DEBUG_FILTERING,
1060                                                 "message score [ %d ] is equal to [ %d ]\n",
1061                                                 info->score, prop->value);
1062                         } else {
1063                                 log_print(LOG_DEBUG_FILTERING,
1064                                                 "message score [ %d ] is not equal to [ %d ]\n",
1065                                                 info->score, prop->value);
1066                         }
1067                 }
1068                 return ret;
1069         }
1070         case MATCHCRITERIA_SIZE_GREATER:
1071         {
1072                 /* FIXME: info->size is a goffset */
1073                 gboolean ret = (info->size > (goffset) prop->value);
1074
1075                 /* debug output */
1076                 if (debug_filtering_session
1077                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1078                         if (ret) {
1079                                 log_print(LOG_DEBUG_FILTERING,
1080                                                 "message size is greater than [ %d ]\n",
1081                                                 prop->value);
1082                         } else {
1083                                 log_print(LOG_DEBUG_FILTERING,
1084                                                 "message size is not greater than [ %d ]\n",
1085                                                 prop->value);
1086                         }
1087                 }
1088                 return ret;
1089         }
1090         case MATCHCRITERIA_SIZE_SMALLER:
1091         {
1092                 /* FIXME: info->size is a goffset */
1093                 gboolean ret = (info->size < (goffset) prop->value);
1094
1095                 /* debug output */
1096                 if (debug_filtering_session
1097                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1098                         if (ret) {
1099                                 log_print(LOG_DEBUG_FILTERING,
1100                                                 "message size is smaller than [ %d ]\n",
1101                                                 prop->value);
1102                         } else {
1103                                 log_print(LOG_DEBUG_FILTERING,
1104                                                 "message size is not smaller than [ %d ]\n",
1105                                                 prop->value);
1106                         }
1107                 }
1108                 return ret;
1109         }
1110         case MATCHCRITERIA_SIZE_EQUAL:
1111         {
1112                 /* FIXME: info->size is a goffset */
1113                 gboolean ret = (info->size == (goffset) prop->value);
1114
1115                 /* debug output */
1116                 if (debug_filtering_session
1117                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1118                         if (ret) {
1119                                 log_print(LOG_DEBUG_FILTERING,
1120                                                 "message size is equal to [ %d ]\n",
1121                                                 prop->value);
1122                         } else {
1123                                 log_print(LOG_DEBUG_FILTERING,
1124                                                 "message size is not equal to [ %d ]\n",
1125                                                 prop->value);
1126                         }
1127                 }
1128                 return ret;
1129         }
1130         case MATCHCRITERIA_PARTIAL:
1131         {
1132                 /* FIXME: info->size is a goffset */
1133                 gboolean ret = (info->total_size != 0 && info->size != (goffset)info->total_size);
1134
1135                 /* debug output */
1136                 if (debug_filtering_session
1137                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1138                         if (ret) {
1139                                 log_print(LOG_DEBUG_FILTERING,
1140                                                 "message is partially downloaded, size is less than total size [ %d ])\n",
1141                                                 info->total_size);
1142                         } else {
1143                                 log_print(LOG_DEBUG_FILTERING,
1144                                                 "message is not partially downloaded\n");
1145                         }
1146                 }
1147                 return ret;
1148         }
1149         case MATCHCRITERIA_NOT_PARTIAL:
1150         {
1151                 /* FIXME: info->size is a goffset */
1152                 gboolean ret = (info->total_size == 0 || info->size == (goffset)info->total_size);
1153
1154                 /* debug output */
1155                 if (debug_filtering_session
1156                                 && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1157                         if (ret) {
1158                                 log_print(LOG_DEBUG_FILTERING,
1159                                                 "message is not partially downloaded\n");
1160                         } else {
1161                                 log_print(LOG_DEBUG_FILTERING,
1162                                                 "message is partially downloaded, size is less than total size [ %d ])\n",
1163                                                 info->total_size);
1164                         }
1165                 }
1166                 return ret;
1167         }
1168         case MATCHCRITERIA_NEWSGROUPS:
1169                 return matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
1170         case MATCHCRITERIA_NOT_NEWSGROUPS:
1171                 return !matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
1172         case MATCHCRITERIA_MESSAGEID:
1173                 return matcherprop_string_match(prop, info->msgid, context_str[CONTEXT_MESSAGEID]);
1174         case MATCHCRITERIA_NOT_MESSAGEID:
1175                 return !matcherprop_string_match(prop, info->msgid, context_str[CONTEXT_MESSAGEID]);
1176         case MATCHCRITERIA_INREPLYTO:
1177                 return matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
1178         case MATCHCRITERIA_NOT_INREPLYTO:
1179                 return !matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
1180         case MATCHCRITERIA_REFERENCES:
1181                 return matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
1182         case MATCHCRITERIA_NOT_REFERENCES:
1183                 return !matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
1184         case MATCHCRITERIA_TEST:
1185                 return matcherprop_match_test(prop, info);
1186         case MATCHCRITERIA_NOT_TEST:
1187                 return !matcherprop_match_test(prop, info);
1188         default:
1189                 return FALSE;
1190         }
1191 }
1192
1193 /* ********************* MatcherList *************************** */
1194
1195 /*!
1196  *\brief        Create a new list of matchers 
1197  *
1198  *\param        matchers List of matcher structures
1199  *\param        bool_and Operator
1200  *
1201  *\return       MatcherList * New list
1202  */
1203 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
1204 {
1205         MatcherList *cond;
1206
1207         cond = g_new0(MatcherList, 1);
1208
1209         cond->matchers = matchers;
1210         cond->bool_and = bool_and;
1211
1212         return cond;
1213 }
1214
1215 #ifdef G_OS_UNIX
1216 /*!
1217  *\brief        Builds a single regular expresion from an array of srings.
1218  *
1219  *\param        strings The lines containing the different sub-regexp.
1220  *
1221  *\return       The newly allocated regexp string.
1222  */
1223 static gchar *build_complete_regexp(gchar **strings)
1224 {
1225         int i = 0;
1226         gchar *expr = NULL;
1227         while (strings && strings[i] && *strings[i]) {
1228                 int old_len = expr ? strlen(expr):0;
1229                 int new_len = 0;
1230                 gchar *tmpstr = NULL;
1231
1232                 if (g_utf8_validate(strings[i], -1, NULL))
1233                         tmpstr = g_strdup(strings[i]);
1234                 else
1235                         tmpstr = conv_codeset_strdup(strings[i], 
1236                                         conv_get_locale_charset_str_no_utf8(),
1237                                         CS_INTERNAL);
1238
1239                 if (strstr(tmpstr, "\n"))
1240                         *(strstr(tmpstr, "\n")) = '\0';
1241
1242                 new_len = strlen(tmpstr);
1243
1244                 expr = g_realloc(expr, 
1245                         expr ? (old_len + strlen("|()") + new_len + 1)
1246                              : (strlen("()") + new_len + 1));
1247                 
1248                 if (old_len) {
1249                         strcpy(expr + old_len, "|(");
1250                         strcpy(expr + old_len + 2, tmpstr);
1251                         strcpy(expr + old_len + 2 + new_len, ")");
1252                 } else {
1253                         strcpy(expr+old_len, "(");
1254                         strcpy(expr+old_len + 1, tmpstr);
1255                         strcpy(expr+old_len + 1 + new_len, ")");
1256                 }
1257                 g_free(tmpstr);
1258                 i++;
1259         }
1260         return expr;
1261 }
1262 #endif
1263
1264 /*!
1265  *\brief        Create a new list of matchers from a multi-line string
1266  *
1267  *\param        lines String with "\n"-separated expressions
1268  *\param        bool_and Operator
1269  *\param        case_sensitive If the matching is case sensitive or not
1270  *
1271  *\return       MatcherList * New matcher list
1272  */
1273 MatcherList *matcherlist_new_from_lines(gchar *lines, gboolean bool_and, 
1274                                         gboolean case_sensitive)
1275 {
1276         MatcherProp *m = NULL;
1277         GSList *matchers = NULL;
1278         gchar **strings = g_strsplit(lines, "\n", -1);
1279
1280 #ifdef G_OS_UNIX
1281         gchar *expr = NULL;
1282         expr = build_complete_regexp(strings);
1283         debug_print("building matcherprop for expr '%s'\n", expr?expr:"NULL");
1284         
1285         m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
1286                         case_sensitive? MATCHTYPE_REGEXP: MATCHTYPE_REGEXPCASE,
1287                         expr, 0);
1288         if (m == NULL) {
1289                 /* print error message */
1290                 debug_print("failed to allocate memory for matcherprop\n");
1291         } else {
1292                 matchers = g_slist_append(matchers, m);
1293         }
1294
1295         g_free(expr);
1296 #else
1297         int i = 0;
1298         while (strings && strings[i] && *strings[i]) {
1299                 m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
1300                         case_sensitive? MATCHTYPE_MATCH: MATCHTYPE_MATCHCASE,
1301                         strings[i], 0);
1302                 if (m == NULL) {
1303                         /* print error message */
1304                         debug_print("failed to allocate memory for matcherprop\n");
1305                 } else {
1306                         matchers = g_slist_append(matchers, m);
1307                 }
1308                 i++;
1309         }
1310 #endif
1311         g_strfreev(strings);
1312
1313         return matcherlist_new(matchers, bool_and);
1314 }
1315
1316 /*!
1317  *\brief        Frees a list of matchers
1318  *
1319  *\param        cond List of matchers
1320  */
1321 void matcherlist_free(MatcherList *cond)
1322 {
1323         GSList *l;
1324
1325         cm_return_if_fail(cond);
1326         for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
1327                 matcherprop_free((MatcherProp *) l->data);
1328         }
1329         g_slist_free(cond->matchers);
1330         g_free(cond);
1331 }
1332
1333 /*!
1334  *\brief        Check if a header matches a matcher condition
1335  *
1336  *\param        matcher Matcher structure to check header for
1337  *\param        buf Header name
1338  *
1339  *\return       boolean TRUE if matching header
1340  */
1341 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
1342                                              gchar *buf)
1343 {
1344         gboolean result = FALSE;
1345         Header *header = NULL;
1346
1347         switch (matcher->criteria) {
1348         case MATCHCRITERIA_HEADER:
1349         case MATCHCRITERIA_NOT_HEADER:
1350                 header = procheader_parse_header(buf);
1351                 if (!header)
1352                         return FALSE;
1353                 if (procheader_headername_equal(header->name,
1354                                                 matcher->header)) {
1355                         if (matcher->criteria == MATCHCRITERIA_HEADER)
1356                                 result = matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
1357                         else
1358                                 result = !matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
1359                         procheader_header_free(header);
1360                         return result;
1361                 }
1362                 else {
1363                         procheader_header_free(header);
1364                 }
1365                 break;
1366         case MATCHCRITERIA_HEADERS_PART:
1367         case MATCHCRITERIA_HEADERS_CONT:
1368         case MATCHCRITERIA_MESSAGE:
1369                 header = procheader_parse_header(buf);
1370                 if (!header)
1371                         return FALSE;
1372                 result = matcherprop_header_line_match(matcher, 
1373                                header->name, header->body,
1374                                (matcher->criteria == MATCHCRITERIA_HEADERS_PART),
1375                                context_str[CONTEXT_HEADER_LINE]);
1376                 procheader_header_free(header);
1377                 return result;
1378         case MATCHCRITERIA_NOT_HEADERS_CONT:
1379         case MATCHCRITERIA_NOT_HEADERS_PART:
1380         case MATCHCRITERIA_NOT_MESSAGE:
1381                 header = procheader_parse_header(buf);
1382                 if (!header)
1383                         return FALSE;
1384                 result = !matcherprop_header_line_match(matcher, 
1385                                header->name, header->body,
1386                                (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART),
1387                                context_str[CONTEXT_HEADER_LINE]);
1388                 procheader_header_free(header);
1389                 return result;
1390         case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1391         case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1392                 {
1393                         GSList *address_list = NULL;
1394                         gint match = MATCH_ONE;
1395                         gboolean found = FALSE;
1396
1397                         /* how many address headers are we trying to match? */
1398                         if (strcasecmp(matcher->header, "Any") == 0)
1399                                 match = MATCH_ANY;
1400                         else if (strcasecmp(matcher->header, "All") == 0)
1401                                         match = MATCH_ALL;
1402
1403                         if (match == MATCH_ONE) {
1404                                 /* matching one address header exactly, is that the right one? */
1405                                 header = procheader_parse_header(buf);
1406                                 if (!header ||
1407                                                 !procheader_headername_equal(header->name, matcher->header)) {
1408                                         procheader_header_free(header);
1409                                         return FALSE;
1410                                 }
1411                                 address_list = address_list_append(address_list, header->body);
1412                                 if (address_list == NULL) {
1413                                         procheader_header_free(header);
1414                                         return FALSE;
1415                                 }
1416                                 procheader_header_free(header);
1417
1418                         } else {
1419                                 header = procheader_parse_header(buf);
1420                                 if (!header)
1421                                         return FALSE;
1422                                 /* address header is one of the headers we have to match when checking
1423                                    for any address header or all address headers? */
1424                                 if (procheader_headername_equal(header->name, "From") ||
1425                                          procheader_headername_equal(header->name, "To") ||
1426                                          procheader_headername_equal(header->name, "Cc") ||
1427                                          procheader_headername_equal(header->name, "Reply-To") ||
1428                                          procheader_headername_equal(header->name, "Sender") ||
1429                                          procheader_headername_equal(header->name, "Resent-From") ||
1430                                          procheader_headername_equal(header->name, "Resent-To"))
1431                                         address_list = address_list_append(address_list, header->body);
1432                                 procheader_header_free(header);
1433                                 if (address_list == NULL)
1434                                         return FALSE;
1435                         }
1436
1437                         found = match_with_addresses_in_addressbook
1438                                                         (matcher, address_list, matcher->criteria,
1439                                                          matcher->expr, match);
1440                         g_slist_free(address_list);
1441
1442                         if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
1443                                 return !found;
1444                         else
1445                                 return found;
1446         }
1447         }
1448
1449         return FALSE;
1450 }
1451
1452 /*!
1453  *\brief        Check if the matcher structure wants headers to
1454  *              be matched
1455  *
1456  *\param        matcher Matcher structure
1457  *
1458  *\return       gboolean TRUE if the matcher structure describes
1459  *              a header match condition
1460  */
1461 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
1462 {
1463         switch (matcher->criteria) {
1464         case MATCHCRITERIA_HEADER:
1465         case MATCHCRITERIA_NOT_HEADER:
1466         case MATCHCRITERIA_HEADERS_PART:
1467         case MATCHCRITERIA_HEADERS_CONT:
1468         case MATCHCRITERIA_NOT_HEADERS_PART:
1469         case MATCHCRITERIA_NOT_HEADERS_CONT:
1470         case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1471         case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1472                 return TRUE;
1473         default:
1474                 return FALSE;
1475         }
1476 }
1477
1478 /*!
1479  *\brief        Check if the matcher structure wants the message
1480  *              to be matched (just perform an action on any
1481  *              message)
1482  *
1483  *\param        matcher Matcher structure
1484  *
1485  *\return       gboolean TRUE if matcher condition should match
1486  *              a message
1487  */
1488 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
1489 {
1490         switch (matcher->criteria) {
1491         case MATCHCRITERIA_MESSAGE:
1492         case MATCHCRITERIA_NOT_MESSAGE:
1493                 return TRUE;
1494         default:
1495                 return FALSE;
1496         }
1497 }
1498
1499 /*!
1500  *\brief        Check if a list of conditions matches one header in
1501  *              a message file.
1502  *
1503  *\param        matchers List of conditions
1504  *\param        fp Message file
1505  *
1506  *\return       gboolean TRUE if one of the headers is matched by
1507  *              the list of conditions. 
1508  */
1509 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
1510 {
1511         GSList *l;
1512         gchar *buf = NULL;
1513         gint ret;
1514
1515         while ((ret = procheader_get_one_field(&buf, fp, NULL)) != -1) {
1516                 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1517                         MatcherProp *matcher = (MatcherProp *) l->data;
1518                         gint match = MATCH_ANY;
1519
1520                         if (matcher->done)
1521                                 continue;
1522
1523                         /* determine the match range (all, any are our concern here) */
1524                         if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
1525                             matcher->criteria == MATCHCRITERIA_NOT_HEADERS_CONT ||
1526                             matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1527                                 match = MATCH_ALL;
1528
1529                         } else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
1530                                            matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
1531                                 Header *header = NULL;
1532
1533                                 /* address header is one of the headers we have to match when checking
1534                                    for any address header or all address headers? */
1535                                 header = procheader_parse_header(buf);
1536                                 if (header &&
1537                                         (procheader_headername_equal(header->name, "From") ||
1538                                          procheader_headername_equal(header->name, "To") ||
1539                                          procheader_headername_equal(header->name, "Cc") ||
1540                                          procheader_headername_equal(header->name, "Reply-To") ||
1541                                          procheader_headername_equal(header->name, "Sender") ||
1542                                          procheader_headername_equal(header->name, "Resent-From") ||
1543                                          procheader_headername_equal(header->name, "Resent-To"))) {
1544
1545                                         if (strcasecmp(matcher->header, "Any") == 0)
1546                                                 match = MATCH_ANY;
1547                                         else if (strcasecmp(matcher->header, "All") == 0)
1548                                                 match = MATCH_ALL;
1549                                         else
1550                                                 match = MATCH_ONE;
1551                                 } else {
1552                                         /* further call to matcherprop_match_one_header() can't match
1553                                            and it irrelevant, so: don't alter the match result */
1554                                         procheader_header_free(header);
1555                                         continue;
1556                                 }
1557                                 procheader_header_free(header);
1558                         }
1559
1560                         /* ZERO line must NOT match for the rule to match.
1561                          */
1562                         if (match == MATCH_ALL) {
1563                                 if (matcherprop_match_one_header(matcher, buf)) {
1564                                         matcher->result = TRUE;
1565                                 } else {
1566                                         matcher->result = FALSE;
1567                                         matcher->done = TRUE;
1568                                 }
1569                         /* else, just one line matching is enough for the rule to match
1570                          */
1571                         } else if (matcherprop_criteria_headers(matcher) ||
1572                                    matcherprop_criteria_message(matcher)) {
1573                                 if (matcherprop_match_one_header(matcher, buf)) {
1574                                         matcher->result = TRUE;
1575                                         matcher->done = TRUE;
1576                                 }
1577                         }
1578                         
1579                         /* if the rule matched and the matchers are OR, no need to
1580                          * check the others */
1581                         if (matcher->result && matcher->done) {
1582                                 if (!matchers->bool_and) {
1583                                         g_free(buf);
1584                                         return TRUE;
1585                                 }
1586                         }
1587                 }
1588                 g_free(buf);
1589                 buf = NULL;
1590         }
1591
1592         return FALSE;
1593 }
1594
1595 /*!
1596  *\brief        Check if a matcher wants to check the message body
1597  *
1598  *\param        matcher Matcher structure
1599  *
1600  *\return       gboolean TRUE if body must be matched.
1601  */
1602 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
1603 {
1604         switch (matcher->criteria) {
1605         case MATCHCRITERIA_BODY_PART:
1606         case MATCHCRITERIA_NOT_BODY_PART:
1607                 return TRUE;
1608         default:
1609                 return FALSE;
1610         }
1611 }
1612         
1613 static gboolean matcherlist_match_binary_content(MatcherList *matchers, MimeInfo *partinfo)
1614 {
1615         FILE *outfp;
1616         gchar buf[BUFFSIZE];
1617         GSList *l;
1618
1619         if (!partinfo || partinfo->type == MIMETYPE_TEXT)
1620                 return FALSE;
1621         else
1622                 outfp = procmime_get_binary_content(partinfo);
1623
1624         if (!outfp)
1625                 return FALSE;
1626
1627         while (claws_fgets(buf, sizeof(buf), outfp) != NULL) {
1628                 strretchomp(buf);
1629
1630                 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1631                         MatcherProp *matcher = (MatcherProp *) l->data;
1632
1633                         if (matcher->done) 
1634                                 continue;
1635
1636                         /* Don't scan non-text parts when looking in body, only
1637                          * when looking in whole message
1638                          */
1639                         if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1640                             matcher->criteria == MATCHCRITERIA_BODY_PART)
1641                                 continue;
1642
1643                         /* if the criteria is ~body_part or ~message, ZERO lines
1644                          * must match for the rule to match.
1645                          */
1646                         if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1647                             matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1648                                 if (matcherprop_string_match(matcher, buf, 
1649                                                         context_str[CONTEXT_BODY_LINE])) {
1650                                         matcher->result = FALSE;
1651                                         matcher->done = TRUE;
1652                                 } else
1653                                         matcher->result = TRUE;
1654                         /* else, just one line has to match */
1655                         } else if (matcherprop_criteria_body(matcher) ||
1656                                    matcherprop_criteria_message(matcher)) {
1657                                 if (matcherprop_string_match(matcher, buf,
1658                                                         context_str[CONTEXT_BODY_LINE])) {
1659                                         matcher->result = TRUE;
1660                                         matcher->done = TRUE;
1661                                 }
1662                         }
1663
1664                         /* if the matchers are OR'ed and the rule matched,
1665                          * no need to check the others. */
1666                         if (matcher->result && matcher->done) {
1667                                 if (!matchers->bool_and) {
1668                                         claws_fclose(outfp);
1669                                         return TRUE;
1670                                 }
1671                         }
1672                 }
1673         }
1674
1675         claws_fclose(outfp);
1676         return FALSE;
1677 }
1678
1679 static gboolean match_content_cb(const gchar *buf, gpointer data)
1680 {
1681         MatcherList *matchers = (MatcherList *)data;
1682         gboolean all_done = TRUE;
1683         GSList *l;
1684
1685         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1686                 MatcherProp *matcher = (MatcherProp *) l->data;
1687
1688                 if (matcher->done) 
1689                         continue;
1690
1691                 /* if the criteria is ~body_part or ~message, ZERO lines
1692                  * must match for the rule to match.
1693                  */
1694                 if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1695                     matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1696                         if (matcherprop_string_match(matcher, buf, 
1697                                                 context_str[CONTEXT_BODY_LINE])) {
1698                                 matcher->result = FALSE;
1699                                 matcher->done = TRUE;
1700                         } else
1701                                 matcher->result = TRUE;
1702                 /* else, just one line has to match */
1703                 } else if (matcherprop_criteria_body(matcher) ||
1704                            matcherprop_criteria_message(matcher)) {
1705                         if (matcherprop_string_match(matcher, buf,
1706                                                 context_str[CONTEXT_BODY_LINE])) {
1707                                 matcher->result = TRUE;
1708                                 matcher->done = TRUE;
1709                         }
1710                 }
1711
1712                 /* if the matchers are OR'ed and the rule matched,
1713                  * no need to check the others. */
1714                 if (matcher->result && matcher->done) {
1715                         if (!matchers->bool_and) {
1716                                 return TRUE;
1717                         }
1718                 }
1719
1720                 if (!matcher->done)
1721                         all_done = FALSE;
1722         }
1723         return all_done;
1724 }
1725
1726 static gboolean matcherlist_match_text_content(MatcherList *matchers, MimeInfo *partinfo)
1727 {
1728         if (partinfo->type != MIMETYPE_TEXT)
1729                 return FALSE;
1730
1731         return procmime_scan_text_content(partinfo, match_content_cb, matchers);
1732 }
1733
1734 /*!
1735  *\brief        Check if a line in a message file's body matches
1736  *              the criteria
1737  *
1738  *\param        matchers List of conditions
1739  *\param        fp Message file
1740  *
1741  *\return       gboolean TRUE if successful match
1742  */
1743 static gboolean matcherlist_match_body(MatcherList *matchers, gboolean body_only, MsgInfo *info)
1744 {
1745         MimeInfo *mimeinfo = NULL;
1746         MimeInfo *partinfo = NULL;
1747         gboolean first_text_found = FALSE;
1748
1749         cm_return_val_if_fail(info != NULL, FALSE);
1750
1751         mimeinfo = procmime_scan_message(info);
1752
1753         /* Skip headers */
1754         partinfo = procmime_mimeinfo_next(mimeinfo);
1755
1756         for (; partinfo != NULL; partinfo = procmime_mimeinfo_next(partinfo)) {
1757
1758                 if (partinfo->type != MIMETYPE_TEXT && body_only)
1759                         continue;
1760
1761                 if (partinfo->type == MIMETYPE_TEXT) {
1762                         first_text_found = TRUE;
1763                         if (matcherlist_match_text_content(matchers, partinfo)) {
1764                                 procmime_mimeinfo_free_all(&mimeinfo);
1765                                 return TRUE;
1766                         }
1767                 } else if (matcherlist_match_binary_content(matchers, partinfo)) {
1768                         procmime_mimeinfo_free_all(&mimeinfo);
1769                         return TRUE;
1770                 }
1771
1772                 if (body_only && first_text_found)
1773                         break;
1774         }
1775         procmime_mimeinfo_free_all(&mimeinfo);
1776
1777         return FALSE;
1778 }
1779
1780 /*!
1781  *\brief        Check if a message file matches criteria
1782  *
1783  *\param        matchers Criteria
1784  *\param        info Message info
1785  *\param        result Default result
1786  *
1787  *\return       gboolean TRUE if matched
1788  */
1789 static gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
1790                                 gboolean result)
1791 {
1792         gboolean read_headers;
1793         gboolean read_body;
1794         gboolean body_only;
1795         GSList *l;
1796         FILE *fp;
1797         gchar *file;
1798
1799         /* file need to be read ? */
1800
1801         read_headers = FALSE;
1802         read_body = FALSE;
1803         body_only = TRUE;
1804         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1805                 MatcherProp *matcher = (MatcherProp *) l->data;
1806
1807                 if (matcherprop_criteria_headers(matcher))
1808                         read_headers = TRUE;
1809                 if (matcherprop_criteria_body(matcher))
1810                         read_body = TRUE;
1811                 if (matcherprop_criteria_message(matcher)) {
1812                         read_headers = TRUE;
1813                         read_body = TRUE;
1814                         body_only = FALSE;
1815                 }
1816                 matcher->result = FALSE;
1817                 matcher->done = FALSE;
1818         }
1819
1820         if (!read_headers && !read_body)
1821                 return result;
1822
1823         file = procmsg_get_message_file_full(info, read_headers, read_body);
1824         if (file == NULL)
1825                 return FALSE;
1826
1827         if ((fp = claws_fopen(file, "rb")) == NULL) {
1828                 FILE_OP_ERROR(file, "claws_fopen");
1829                 g_free(file);
1830                 return result;
1831         }
1832
1833         /* read the headers */
1834
1835         if (read_headers) {
1836                 if (matcherlist_match_headers(matchers, fp))
1837                         read_body = FALSE;
1838         } else {
1839                 procheader_skip_headers(fp);
1840         }
1841
1842         /* read the body */
1843         if (read_body) {
1844                 matcherlist_match_body(matchers, body_only, info);
1845         }
1846         
1847         for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
1848                 MatcherProp *matcher = (MatcherProp *) l->data;
1849
1850                 if (matcherprop_criteria_headers(matcher) ||
1851                     matcherprop_criteria_body(matcher)    ||
1852                     matcherprop_criteria_message(matcher)) {
1853                         if (matcher->result) {
1854                                 if (!matchers->bool_and) {
1855                                         result = TRUE;
1856                                         break;
1857                                 }
1858                         }
1859                         else {
1860                                 if (matchers->bool_and) {
1861                                         result = FALSE;
1862                                         break;
1863                                 }
1864                         }
1865                 }                       
1866         }
1867
1868         g_free(file);
1869
1870         claws_fclose(fp);
1871         
1872         return result;
1873 }
1874
1875 /*!
1876  *\brief        Test list of conditions on a message.
1877  *
1878  *\param        matchers List of conditions
1879  *\param        info Message info
1880  *
1881  *\return       gboolean TRUE if matched
1882  */
1883 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
1884 {
1885         GSList *l;
1886         gboolean result;
1887
1888         if (!matchers)
1889                 return FALSE;
1890
1891         if (matchers->bool_and)
1892                 result = TRUE;
1893         else
1894                 result = FALSE;
1895
1896         /* test the cached elements */
1897
1898         for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
1899                 MatcherProp *matcher = (MatcherProp *) l->data;
1900
1901                 if (debug_filtering_session) {
1902                         gchar *buf = matcherprop_to_string(matcher);
1903                         log_print(LOG_DEBUG_FILTERING, _("checking if message matches [ %s ]\n"), buf);
1904                         g_free(buf);
1905                 }
1906
1907                 switch(matcher->criteria) {
1908                 case MATCHCRITERIA_ALL:
1909                 case MATCHCRITERIA_UNREAD:
1910                 case MATCHCRITERIA_NOT_UNREAD:
1911                 case MATCHCRITERIA_NEW:
1912                 case MATCHCRITERIA_NOT_NEW:
1913                 case MATCHCRITERIA_MARKED:
1914                 case MATCHCRITERIA_NOT_MARKED:
1915                 case MATCHCRITERIA_DELETED:
1916                 case MATCHCRITERIA_NOT_DELETED:
1917                 case MATCHCRITERIA_REPLIED:
1918                 case MATCHCRITERIA_NOT_REPLIED:
1919                 case MATCHCRITERIA_FORWARDED:
1920                 case MATCHCRITERIA_NOT_FORWARDED:
1921                 case MATCHCRITERIA_LOCKED:
1922                 case MATCHCRITERIA_NOT_LOCKED:
1923                 case MATCHCRITERIA_SPAM:
1924                 case MATCHCRITERIA_NOT_SPAM:
1925                 case MATCHCRITERIA_HAS_ATTACHMENT:
1926                 case MATCHCRITERIA_HAS_NO_ATTACHMENT:
1927                 case MATCHCRITERIA_SIGNED:
1928                 case MATCHCRITERIA_NOT_SIGNED:
1929                 case MATCHCRITERIA_COLORLABEL:
1930                 case MATCHCRITERIA_NOT_COLORLABEL:
1931                 case MATCHCRITERIA_IGNORE_THREAD:
1932                 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1933                 case MATCHCRITERIA_WATCH_THREAD:
1934                 case MATCHCRITERIA_NOT_WATCH_THREAD:
1935                 case MATCHCRITERIA_SUBJECT:
1936                 case MATCHCRITERIA_NOT_SUBJECT:
1937                 case MATCHCRITERIA_FROM:
1938                 case MATCHCRITERIA_NOT_FROM:
1939                 case MATCHCRITERIA_TO:
1940                 case MATCHCRITERIA_NOT_TO:
1941                 case MATCHCRITERIA_CC:
1942                 case MATCHCRITERIA_NOT_CC:
1943                 case MATCHCRITERIA_TO_OR_CC:
1944                 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1945                 case MATCHCRITERIA_TAG:
1946                 case MATCHCRITERIA_NOT_TAG:
1947                 case MATCHCRITERIA_TAGGED:
1948                 case MATCHCRITERIA_NOT_TAGGED:
1949                 case MATCHCRITERIA_AGE_GREATER:
1950                 case MATCHCRITERIA_AGE_LOWER:
1951                 case MATCHCRITERIA_AGE_GREATER_HOURS:
1952                 case MATCHCRITERIA_AGE_LOWER_HOURS:
1953                 case MATCHCRITERIA_DATE_AFTER:
1954                 case MATCHCRITERIA_DATE_BEFORE:
1955                 case MATCHCRITERIA_NEWSGROUPS:
1956                 case MATCHCRITERIA_NOT_NEWSGROUPS:
1957                 case MATCHCRITERIA_MESSAGEID:
1958                 case MATCHCRITERIA_NOT_MESSAGEID:
1959                 case MATCHCRITERIA_INREPLYTO:
1960                 case MATCHCRITERIA_NOT_INREPLYTO:
1961                 case MATCHCRITERIA_REFERENCES:
1962                 case MATCHCRITERIA_NOT_REFERENCES:
1963                 case MATCHCRITERIA_SCORE_GREATER:
1964                 case MATCHCRITERIA_SCORE_LOWER:
1965                 case MATCHCRITERIA_SCORE_EQUAL:
1966                 case MATCHCRITERIA_SIZE_GREATER:
1967                 case MATCHCRITERIA_SIZE_SMALLER:
1968                 case MATCHCRITERIA_SIZE_EQUAL:
1969                 case MATCHCRITERIA_TEST:
1970                 case MATCHCRITERIA_NOT_TEST:
1971                 case MATCHCRITERIA_PARTIAL:
1972                 case MATCHCRITERIA_NOT_PARTIAL:
1973                         if (matcherprop_match(matcher, info)) {
1974                                 if (!matchers->bool_and) {
1975                                         if (debug_filtering_session)
1976                                                 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1977                                         return TRUE;
1978                                 }
1979                         }
1980                         else {
1981                                 if (matchers->bool_and) {
1982                                         if (debug_filtering_session)
1983                                                 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1984                                         return FALSE;
1985                                 }
1986                         }
1987                 }
1988         }
1989
1990         /* test the condition on the file */
1991
1992         if (matcherlist_match_file(matchers, info, result)) {
1993                 if (!matchers->bool_and) {
1994                         if (debug_filtering_session)
1995                                 log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1996                         return TRUE;
1997                 }
1998         } else {
1999                 if (matchers->bool_and) {
2000                         if (debug_filtering_session)
2001                                 log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
2002                         return FALSE;
2003                 }
2004         }
2005
2006         if (debug_filtering_session) {
2007                 if (result)
2008                         log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
2009                 else
2010                         log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
2011         }
2012         return result;
2013 }
2014
2015
2016 static gint quote_filter_str(gchar * result, guint size,
2017                              const gchar * path)
2018 {
2019         const gchar * p;
2020         gchar * result_p;
2021         guint remaining;
2022
2023         result_p = result;
2024         remaining = size;
2025
2026         for(p = path ; * p != '\0' ; p ++) {
2027
2028                 if ((* p != '\"') && (* p != '\\')) {
2029                         if (remaining > 0) {
2030                                 * result_p = * p;
2031                                 result_p ++; 
2032                                 remaining --;
2033                         }
2034                         else {
2035                                 result[size - 1] = '\0';
2036                                 return -1;
2037                         }
2038                 }
2039                 else { 
2040                         if (remaining >= 2) {
2041                                 * result_p = '\\';
2042                                 result_p ++; 
2043                                 * result_p = * p;
2044                                 result_p ++; 
2045                                 remaining -= 2;
2046                         }
2047                         else {
2048                                 result[size - 1] = '\0';
2049                                 return -1;
2050                         }
2051                 }
2052         }
2053         if (remaining > 0) {
2054                 * result_p = '\0';
2055         }
2056         else {
2057                 result[size - 1] = '\0';
2058                 return -1;
2059         }
2060   
2061         return 0;
2062 }
2063
2064
2065 gchar * matcher_quote_str(const gchar * src)
2066 {
2067         gchar * res;
2068         gint len;
2069         
2070         len = strlen(src) * 2 + 1;
2071         res = g_malloc(len);
2072         quote_filter_str(res, len, src);
2073         
2074         return res;
2075 }
2076
2077 /*!
2078  *\brief        Convert a matcher structure to a string
2079  *
2080  *\param        matcher Matcher structure
2081  *
2082  *\return       gchar * Newly allocated string
2083  */
2084 gchar *matcherprop_to_string(MatcherProp *matcher)
2085 {
2086         gchar *matcher_str = NULL;
2087         const gchar *criteria_str;
2088         const gchar *matchtype_str;
2089         int i;
2090         gchar * quoted_expr;
2091         gchar * quoted_header;
2092         
2093         criteria_str = NULL;
2094         for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
2095                 if (matchparser_tab[i].id == matcher->criteria)
2096                         criteria_str = matchparser_tab[i].str;
2097         }
2098         if (criteria_str == NULL)
2099                 return NULL;
2100
2101         switch (matcher->criteria) {
2102         case MATCHCRITERIA_AGE_GREATER:
2103         case MATCHCRITERIA_AGE_LOWER:
2104         case MATCHCRITERIA_AGE_GREATER_HOURS:
2105         case MATCHCRITERIA_AGE_LOWER_HOURS:
2106         case MATCHCRITERIA_SCORE_GREATER:
2107         case MATCHCRITERIA_SCORE_LOWER:
2108         case MATCHCRITERIA_SCORE_EQUAL:
2109         case MATCHCRITERIA_SIZE_GREATER:
2110         case MATCHCRITERIA_SIZE_SMALLER:
2111         case MATCHCRITERIA_SIZE_EQUAL:
2112         case MATCHCRITERIA_COLORLABEL:
2113         case MATCHCRITERIA_NOT_COLORLABEL:
2114                 return g_strdup_printf("%s %i", criteria_str, matcher->value);
2115         case MATCHCRITERIA_ALL:
2116         case MATCHCRITERIA_UNREAD:
2117         case MATCHCRITERIA_NOT_UNREAD:
2118         case MATCHCRITERIA_NEW:
2119         case MATCHCRITERIA_NOT_NEW:
2120         case MATCHCRITERIA_MARKED:
2121         case MATCHCRITERIA_NOT_MARKED:
2122         case MATCHCRITERIA_DELETED:
2123         case MATCHCRITERIA_NOT_DELETED:
2124         case MATCHCRITERIA_REPLIED:
2125         case MATCHCRITERIA_NOT_REPLIED:
2126         case MATCHCRITERIA_FORWARDED:
2127         case MATCHCRITERIA_NOT_FORWARDED:
2128         case MATCHCRITERIA_LOCKED:
2129         case MATCHCRITERIA_NOT_LOCKED:
2130         case MATCHCRITERIA_SPAM:
2131         case MATCHCRITERIA_NOT_SPAM:
2132         case MATCHCRITERIA_HAS_ATTACHMENT:
2133         case MATCHCRITERIA_HAS_NO_ATTACHMENT:
2134         case MATCHCRITERIA_SIGNED:
2135         case MATCHCRITERIA_NOT_SIGNED:
2136         case MATCHCRITERIA_PARTIAL:
2137         case MATCHCRITERIA_NOT_PARTIAL:
2138         case MATCHCRITERIA_IGNORE_THREAD:
2139         case MATCHCRITERIA_NOT_IGNORE_THREAD:
2140         case MATCHCRITERIA_WATCH_THREAD:
2141         case MATCHCRITERIA_NOT_WATCH_THREAD:
2142         case MATCHCRITERIA_TAGGED:
2143         case MATCHCRITERIA_NOT_TAGGED:
2144                 return g_strdup(criteria_str);
2145         case MATCHCRITERIA_TEST:
2146         case MATCHCRITERIA_NOT_TEST:
2147         case MATCHCRITERIA_DATE_AFTER:
2148         case MATCHCRITERIA_DATE_BEFORE:
2149                 quoted_expr = matcher_quote_str(matcher->expr);
2150                 matcher_str = g_strdup_printf("%s \"%s\"",
2151                                               criteria_str, quoted_expr);
2152                 g_free(quoted_expr);
2153                 return matcher_str;
2154         case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
2155         case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
2156                 quoted_header = matcher_quote_str(matcher->header);
2157                 quoted_expr = matcher_quote_str(matcher->expr);
2158                 matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
2159                                               criteria_str, quoted_header, quoted_expr);
2160                 g_free(quoted_header);
2161                 g_free(quoted_expr);
2162                 return matcher_str;
2163         }
2164
2165         matchtype_str = NULL;
2166         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
2167                 if (matchparser_tab[i].id == matcher->matchtype)
2168                         matchtype_str = matchparser_tab[i].str;
2169         }
2170
2171         if (matchtype_str == NULL)
2172                 return NULL;
2173
2174         switch (matcher->matchtype) {
2175         case MATCHTYPE_MATCH:
2176         case MATCHTYPE_MATCHCASE:
2177         case MATCHTYPE_REGEXP:
2178         case MATCHTYPE_REGEXPCASE:
2179                 quoted_expr = matcher_quote_str(matcher->expr);
2180                 if (matcher->header) {
2181                         quoted_header = matcher_quote_str(matcher->header);
2182                         matcher_str = g_strdup_printf
2183                                         ("%s \"%s\" %s \"%s\"",
2184                                          criteria_str, quoted_header,
2185                                          matchtype_str, quoted_expr);
2186                         g_free(quoted_header);
2187                 }
2188                 else
2189                         matcher_str = g_strdup_printf
2190                                         ("%s %s \"%s\"", criteria_str,
2191                                          matchtype_str, quoted_expr);
2192                 g_free(quoted_expr);
2193                 break;
2194         }
2195
2196         return matcher_str;
2197 }
2198
2199 /*!
2200  *\brief        Convert a list of conditions to a string
2201  *
2202  *\param        matchers List of conditions
2203  *
2204  *\return       gchar * Newly allocated string
2205  */
2206 gchar *matcherlist_to_string(const MatcherList *matchers)
2207 {
2208         gint count;
2209         gchar **vstr;
2210         GSList *l;
2211         gchar **cur_str;
2212         gchar *result = NULL;
2213
2214         count = g_slist_length(matchers->matchers);
2215         vstr = g_new(gchar *, count + 1);
2216
2217         for (l = matchers->matchers, cur_str = vstr; l != NULL;
2218              l = g_slist_next(l), cur_str ++) {
2219                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
2220                 if (*cur_str == NULL)
2221                         break;
2222         }
2223         *cur_str = NULL;
2224         
2225         if (matchers->bool_and)
2226                 result = g_strjoinv(" & ", vstr);
2227         else
2228                 result = g_strjoinv(" | ", vstr);
2229
2230         for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
2231                 g_free(*cur_str);
2232         g_free(vstr);
2233
2234         return result;
2235 }
2236
2237
2238 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
2239 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
2240
2241 static void add_str_default(gchar ** dest,
2242                             const gchar * s, const gchar * d)
2243 {
2244         gchar quoted_str[4096];
2245         const gchar * str;
2246         
2247         if (s != NULL)
2248                 str = s;
2249         else
2250                 str = d;
2251         
2252         quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
2253         strcpy(* dest, quoted_str);
2254         
2255         (* dest) += strlen(* dest);
2256 }
2257
2258 /* matching_build_command() - preferably cmd should be unescaped */
2259 /*!
2260  *\brief        Build the command-line to execute
2261  *
2262  *\param        cmd String with command-line specifiers
2263  *\param        info Message info to use for command
2264  *
2265  *\return       gchar * Newly allocated string
2266  */
2267 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
2268 {
2269         const gchar *s = cmd;
2270         gchar *filename = NULL;
2271         gchar *processed_cmd;
2272         gchar *p;
2273         gint size;
2274
2275         const gchar *const no_subject    = _("(none)") ;
2276         const gchar *const no_from       = _("(none)") ;
2277         const gchar *const no_to         = _("(none)") ;
2278         const gchar *const no_cc         = _("(none)") ;
2279         const gchar *const no_date       = _("(none)") ;
2280         const gchar *const no_msgid      = _("(none)") ;
2281         const gchar *const no_newsgroups = _("(none)") ;
2282         const gchar *const no_references = _("(none)") ;
2283
2284         size = STRLEN_ZERO(cmd) + 1;
2285         while (*s != '\0') {
2286                 if (*s == '%') {
2287                         s++;
2288                         switch (*s) {
2289                         case '%':
2290                                 size -= 1;
2291                                 break;
2292                         case 's': /* subject */
2293                                 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
2294                                 break;
2295                         case 'f': /* from */
2296                                 size += STRLEN_DEFAULT(info->from, no_from) - 2;
2297                                 break;
2298                         case 't': /* to */
2299                                 size += STRLEN_DEFAULT(info->to, no_to) - 2;
2300                                 break;
2301                         case 'c': /* cc */
2302                                 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
2303                                 break;
2304                         case 'd': /* date */
2305                                 size += STRLEN_DEFAULT(info->date, no_date) - 2;
2306                                 break;
2307                         case 'i': /* message-id */
2308                                 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
2309                                 break;
2310                         case 'n': /* newsgroups */
2311                                 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
2312                                 break;
2313                         case 'r': /* references */
2314                                 /* FIXME: using the inreplyto header for reference */
2315                                 size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
2316                                 break;
2317                         case 'F': /* file */
2318                                 if (filename == NULL)
2319                                         filename = folder_item_fetch_msg(info->folder, info->msgnum);
2320                                 
2321                                 if (filename == NULL) {
2322                                         g_warning("filename is not set");
2323                                         return NULL;
2324                                 }
2325                                 else {
2326                                         size += strlen(filename) - 2;
2327                                 }
2328                                 break;
2329                         }
2330                         s++;
2331                 }
2332                 else s++;
2333         }
2334         
2335         /* as the string can be quoted, we double the result */
2336         size *= 2;
2337
2338         processed_cmd = g_new0(gchar, size);
2339         s = cmd;
2340         p = processed_cmd;
2341
2342         while (*s != '\0') {
2343                 if (*s == '%') {
2344                         s++;
2345                         switch (*s) {
2346                         case '%':
2347                                 *p = '%';
2348                                 p++;
2349                                 break;
2350                         case 's': /* subject */
2351                                 add_str_default(&p, info->subject,
2352                                                 no_subject);
2353                                 break;
2354                         case 'f': /* from */
2355                                 add_str_default(&p, info->from,
2356                                                 no_from);
2357                                 break;
2358                         case 't': /* to */
2359                                 add_str_default(&p, info->to,
2360                                                 no_to);
2361                                 break;
2362                         case 'c': /* cc */
2363                                 add_str_default(&p, info->cc,
2364                                                 no_cc);
2365                                 break;
2366                         case 'd': /* date */
2367                                 add_str_default(&p, info->date,
2368                                                 no_date);
2369                                 break;
2370                         case 'i': /* message-id */
2371                                 add_str_default(&p, info->msgid,
2372                                                 no_msgid);
2373                                 break;
2374                         case 'n': /* newsgroups */
2375                                 add_str_default(&p, info->newsgroups,
2376                                                 no_newsgroups);
2377                                 break;
2378                         case 'r': /* references */
2379                                 /* FIXME: using the inreplyto header for references */
2380                                 add_str_default(&p, info->inreplyto, no_references);
2381                                 break;
2382                         case 'F': /* file */
2383                                 if (filename != NULL)
2384                                         add_str_default(&p, filename, NULL);
2385                                 break;
2386                         default:
2387                                 *p = '%';
2388                                 p++;
2389                                 *p = *s;
2390                                 p++;
2391                                 break;
2392                         }
2393                         s++;
2394                 }
2395                 else {
2396                         *p = *s;
2397                         p++;
2398                         s++;
2399                 }
2400         }
2401         g_free(filename);
2402         
2403         return processed_cmd;
2404 }
2405 #undef STRLEN_DEFAULT
2406 #undef STRLEN_ZERO
2407
2408 /* ************************************************************ */
2409
2410
2411 /*!
2412  *\brief        Write filtering list to file
2413  *
2414  *\param        fp File
2415  *\param        prefs_filtering List of filtering conditions
2416  */
2417 static int prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
2418 {
2419         GSList *cur = NULL;
2420
2421         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
2422                 gchar *filtering_str = NULL;
2423                 gchar *tmp_name = NULL;
2424                 FilteringProp *prop = NULL;
2425
2426                 if (NULL == (prop = (FilteringProp *) cur->data))
2427                         continue;
2428                 
2429                 if (NULL == (filtering_str = filteringprop_to_string(prop)))
2430                         continue;
2431
2432                 if (prop->enabled) {
2433                         if (claws_fputs("enabled ", fp) == EOF) {
2434                                 FILE_OP_ERROR("filtering config", "claws_fputs");
2435                                 return -1;
2436                         }
2437                 } else {
2438                         if (claws_fputs("disabled ", fp) == EOF) {
2439                                 FILE_OP_ERROR("filtering config", "claws_fputs");
2440                                 return -1;
2441                         }
2442                 }
2443
2444                 if (claws_fputs("rulename \"", fp) == EOF) {
2445                         FILE_OP_ERROR("filtering config", "claws_fputs");
2446                         g_free(filtering_str);
2447                         return -1;
2448                 }
2449                 tmp_name = prop->name;
2450                 while (tmp_name && *tmp_name != '\0') {
2451                         if (*tmp_name != '"') {
2452                                 if (claws_fputc(*tmp_name, fp) == EOF) {
2453                                         FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2454                                         g_free(filtering_str);
2455                                         return -1;
2456                                 }
2457                         } else if (*tmp_name == '"') {
2458                                 if (claws_fputc('\\', fp) == EOF ||
2459                                     claws_fputc('"', fp) == EOF) {
2460                                         FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2461                                         g_free(filtering_str);
2462                                         return -1;
2463                                 }
2464                         }
2465                         tmp_name ++;
2466                 }
2467                 if (claws_fputs("\" ", fp) == EOF) {
2468                         FILE_OP_ERROR("filtering config", "claws_fputs");
2469                         g_free(filtering_str);
2470                         return -1;
2471                 }
2472
2473                 if (prop->account_id != 0) {
2474                         gchar *tmp = NULL;
2475
2476                         tmp = g_strdup_printf("account %d ", prop->account_id);
2477                         if (claws_fputs(tmp, fp) == EOF) {
2478                                 FILE_OP_ERROR("filtering config", "claws_fputs");
2479                                 g_free(tmp);
2480                                 return -1;
2481                         }
2482                         g_free(tmp);
2483                 }
2484
2485                 if(claws_fputs(filtering_str, fp) == EOF ||
2486                     claws_fputc('\n', fp) == EOF) {
2487                         FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2488                         g_free(filtering_str);
2489                         return -1;
2490                 }
2491                 g_free(filtering_str);
2492         }
2493         
2494         return 0;
2495 }
2496
2497 typedef struct _NodeLoopData {
2498         FILE *fp;
2499         gboolean error;
2500 } NodeLoopData;
2501
2502 /*!
2503  *\brief        Write matchers from a folder item
2504  *
2505  *\param        node Node with folder info
2506  *\param        data File pointer
2507  *
2508  *\return       gboolean FALSE
2509  */
2510 static gboolean prefs_matcher_write_func(GNode *node, gpointer d)
2511 {
2512         FolderItem *item;
2513         NodeLoopData *data = (NodeLoopData *)d;
2514         gchar *id;
2515         GSList *prefs_filtering;
2516
2517         item = node->data;
2518         /* prevent warning */
2519         if (item->path == NULL)
2520                 return FALSE;
2521         id = folder_item_get_identifier(item);
2522         if (id == NULL)
2523                 return FALSE;
2524         prefs_filtering = item->prefs->processing;
2525
2526         if (prefs_filtering != NULL) {
2527                 if (fprintf(data->fp, "[%s]\n", id) < 0) {
2528                         data->error = TRUE;
2529                         goto fail;
2530                 }
2531                 if (prefs_filtering_write(data->fp, prefs_filtering) < 0) {
2532                         data->error = TRUE;
2533                         goto fail;
2534                 }
2535                 if (claws_fputc('\n', data->fp) == EOF) {
2536                         data->error = TRUE;
2537                         goto fail;
2538                 }
2539         }
2540 fail:
2541         g_free(id);
2542
2543         return FALSE;
2544 }
2545
2546 /*!
2547  *\brief        Save matchers from folder items
2548  *
2549  *\param        fp File
2550  */
2551 static int prefs_matcher_save(FILE *fp)
2552 {
2553         GList *cur;
2554         NodeLoopData data;
2555         
2556         data.fp = fp;
2557         data.error = FALSE;
2558
2559         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
2560                 Folder *folder;
2561
2562                 folder = (Folder *) cur->data;
2563                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2564                                 prefs_matcher_write_func, &data);
2565         }
2566         
2567         if (data.error == TRUE)
2568                 return -1;
2569
2570         /* pre global rules */
2571         if (fprintf(fp, "[preglobal]\n") < 0 ||
2572             prefs_filtering_write(fp, pre_global_processing) < 0 ||
2573             claws_fputc('\n', fp) == EOF)
2574                 return -1;
2575
2576         /* post global rules */
2577         if (fprintf(fp, "[postglobal]\n") < 0 ||
2578             prefs_filtering_write(fp, post_global_processing) < 0 ||
2579             claws_fputc('\n', fp) == EOF)
2580                 return -1;
2581         
2582         /* filtering rules */
2583         if (fprintf(fp, "[filtering]\n") < 0 ||
2584             prefs_filtering_write(fp, filtering_rules) < 0 ||
2585             claws_fputc('\n', fp) == EOF)
2586                 return -1;
2587
2588         return 0;
2589 }
2590
2591 /*!
2592  *\brief        Write filtering / matcher configuration file
2593  */
2594 void prefs_matcher_write_config(void)
2595 {
2596         gchar *rcpath;
2597         PrefFile *pfile;
2598
2599         debug_print("Writing matcher configuration...\n");
2600
2601         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2602                              MATCHER_RC, NULL);
2603
2604         if ((pfile = prefs_write_open(rcpath)) == NULL) {
2605                 g_warning("failed to write configuration to file");
2606                 g_free(rcpath);
2607                 return;
2608         }
2609
2610         g_free(rcpath);
2611
2612         if (prefs_matcher_save(pfile->fp) < 0) {
2613                 g_warning("failed to write configuration to file");
2614                 prefs_file_close_revert(pfile);
2615         } else if (prefs_file_close(pfile) < 0) {
2616                 g_warning("failed to save configuration to file");
2617         }
2618 }
2619
2620 /*!
2621  *\brief        Read matcher configuration
2622  */
2623 void prefs_matcher_read_config(void)
2624 {
2625         gchar *rcpath;
2626         FILE *f;
2627
2628         create_matchparser_hashtab();
2629         prefs_filtering_clear();
2630
2631         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
2632
2633         f = claws_fopen(rcpath, "rb");
2634         g_free(rcpath);
2635
2636         if (f != NULL) {
2637                 matcher_parser_start_parsing(f);
2638                 claws_fclose(matcher_parserin);
2639         }
2640 }