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