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