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