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