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