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