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