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