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