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