3cbc7a8ecced74bc2d1ed79d8283f0a27e615fe5
[claws.git] / src / matcher.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002-2004 by the Sylpheed Claws 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 "sylpheed.h"
45 #include <ctype.h>
46
47 /*!
48  *\brief        Keyword lookup element
49  */
50 struct _MatchParser {
51         gint id;                /*!< keyword id */ 
52         gchar *str;             /*!< keyword */
53 };
54 typedef struct _MatchParser MatchParser;
55
56 /*!
57  *\brief        Table with strings and ids used by the lexer and
58  *              the parser. New keywords can be added here.
59  */
60 static const MatchParser matchparser_tab[] = {
61         /* msginfo flags */
62         {MATCHCRITERIA_ALL, "all"},
63         {MATCHCRITERIA_UNREAD, "unread"},
64         {MATCHCRITERIA_NOT_UNREAD, "~unread"},
65         {MATCHCRITERIA_NEW, "new"},
66         {MATCHCRITERIA_NOT_NEW, "~new"},
67         {MATCHCRITERIA_MARKED, "marked"},
68         {MATCHCRITERIA_NOT_MARKED, "~marked"},
69         {MATCHCRITERIA_DELETED, "deleted"},
70         {MATCHCRITERIA_NOT_DELETED, "~deleted"},
71         {MATCHCRITERIA_REPLIED, "replied"},
72         {MATCHCRITERIA_NOT_REPLIED, "~replied"},
73         {MATCHCRITERIA_FORWARDED, "forwarded"},
74         {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
75         {MATCHCRITERIA_LOCKED, "locked"},
76         {MATCHCRITERIA_NOT_LOCKED, "~locked"},
77         {MATCHCRITERIA_COLORLABEL, "colorlabel"},
78         {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
79         {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
80         {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
81
82         /* msginfo headers */
83         {MATCHCRITERIA_SUBJECT, "subject"},
84         {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
85         {MATCHCRITERIA_FROM, "from"},
86         {MATCHCRITERIA_NOT_FROM, "~from"},
87         {MATCHCRITERIA_TO, "to"},
88         {MATCHCRITERIA_NOT_TO, "~to"},
89         {MATCHCRITERIA_CC, "cc"},
90         {MATCHCRITERIA_NOT_CC, "~cc"},
91         {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
92         {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
93         {MATCHCRITERIA_AGE_GREATER, "age_greater"},
94         {MATCHCRITERIA_AGE_LOWER, "age_lower"},
95         {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
96         {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
97         {MATCHCRITERIA_INREPLYTO, "inreplyto"},
98         {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
99         {MATCHCRITERIA_REFERENCES, "references"},
100         {MATCHCRITERIA_NOT_REFERENCES, "~references"},
101         {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
102         {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
103         {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
104         {MATCHCRITERIA_PARTIAL, "partial"},
105         {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
106
107         {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
108         {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
109         {MATCHCRITERIA_SIZE_EQUAL,   "size_equal"},
110
111         /* content have to be read */
112         {MATCHCRITERIA_HEADER, "header"},
113         {MATCHCRITERIA_NOT_HEADER, "~header"},
114         {MATCHCRITERIA_HEADERS_PART, "headers_part"},
115         {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
116         {MATCHCRITERIA_MESSAGE, "message"},
117         {MATCHCRITERIA_NOT_MESSAGE, "~message"},
118         {MATCHCRITERIA_BODY_PART, "body_part"},
119         {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
120         {MATCHCRITERIA_TEST, "test"},
121         {MATCHCRITERIA_NOT_TEST, "~test"},
122
123         /* match type */
124         {MATCHTYPE_MATCHCASE, "matchcase"},
125         {MATCHTYPE_MATCH, "match"},
126         {MATCHTYPE_REGEXPCASE, "regexpcase"},
127         {MATCHTYPE_REGEXP, "regexp"},
128         {MATCHTYPE_ANY_IN_ADDRESSBOOK, "any_in_addressbook"},
129         {MATCHTYPE_ALL_IN_ADDRESSBOOK, "all_in_addressbook"},
130
131         /* actions */
132         {MATCHACTION_SCORE, "score"},    /* for backward compatibility */
133         {MATCHACTION_MOVE, "move"},
134         {MATCHACTION_COPY, "copy"},
135         {MATCHACTION_DELETE, "delete"},
136         {MATCHACTION_MARK, "mark"},
137         {MATCHACTION_UNMARK, "unmark"},
138         {MATCHACTION_LOCK, "lock"},
139         {MATCHACTION_UNLOCK, "unlock"},
140         {MATCHACTION_MARK_AS_READ, "mark_as_read"},
141         {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
142         {MATCHACTION_FORWARD, "forward"},
143         {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
144         {MATCHACTION_EXECUTE, "execute"},
145         {MATCHACTION_COLOR, "color"},
146         {MATCHACTION_REDIRECT, "redirect"},
147         {MATCHACTION_CHANGE_SCORE, "change_score"},
148         {MATCHACTION_SET_SCORE, "set_score"},
149         {MATCHACTION_STOP, "stop"},
150         {MATCHACTION_HIDE, "hide"},
151         {MATCHACTION_IGNORE, "ignore"},
152 };
153
154 /*!
155  *\brief        Look up table with keywords defined in \sa matchparser_tab
156  */
157 static GHashTable *matchparser_hashtab;
158
159 /*!
160  *\brief        Translate keyword id to keyword string
161  *
162  *\param        id Id of keyword
163  *
164  *\return       const gchar * Keyword
165  */
166 const gchar *get_matchparser_tab_str(gint id)
167 {
168         gint i;
169
170         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
171                 if (matchparser_tab[i].id == id)
172                         return matchparser_tab[i].str;
173         }
174         return NULL;
175 }
176
177 /*!
178  *\brief        Create keyword lookup table
179  */
180 static void create_matchparser_hashtab(void)
181 {
182         int i;
183         
184         if (matchparser_hashtab) return;
185         matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
186         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
187                 g_hash_table_insert(matchparser_hashtab,
188                                     matchparser_tab[i].str,
189                                     (gpointer) &matchparser_tab[i]);
190 }
191
192 /*!
193  *\brief        Return a keyword id from a keyword string
194  *
195  *\param        str Keyword string
196  *
197  *\return       gint Keyword id
198  */
199 gint get_matchparser_tab_id(const gchar *str)
200 {
201         MatchParser *res;
202
203         if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
204                 return res->id;
205         } else
206                 return -1;
207 }
208
209 /* **************** data structure allocation **************** */
210
211 /*!
212  *\brief        Allocate a structure for a filtering / scoring
213  *              "condition" (a matcher structure)
214  *
215  *\param        criteria Criteria ID (MATCHCRITERIA_XXXX)
216  *\param        header Header string (if criteria is MATCHCRITERIA_HEADER)
217  *\param        matchtype Type of action (MATCHTYPE_XXX)
218  *\param        expr String value or expression to check
219  *\param        value Integer value to check
220  *
221  *\return       MatcherProp * Pointer to newly allocated structure
222  */
223 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
224                               gint matchtype, const gchar *expr,
225                               int value)
226 {
227         MatcherProp *prop;
228
229         prop = g_new0(MatcherProp, 1);
230         prop->criteria = criteria;
231         prop->header = header != NULL ? g_strdup(header) : NULL;
232         prop->expr = expr != NULL ? g_strdup(expr) : NULL;
233         prop->matchtype = matchtype;
234         prop->preg = NULL;
235         prop->value = value;
236         prop->error = 0;
237
238         return prop;
239 }
240
241 /*!
242  *\brief        Free a matcher structure
243  *
244  *\param        prop Pointer to matcher structure allocated with
245  *              #matcherprop_new
246  */
247 void matcherprop_free(MatcherProp *prop)
248 {
249         g_free(prop->expr);
250         g_free(prop->header);
251         if (prop->preg != NULL) {
252                 regfree(prop->preg);
253                 g_free(prop->preg);
254         }
255         g_free(prop);
256 }
257
258 /*!
259  *\brief        Copy a matcher structure
260  *
261  *\param        src Matcher structure to copy
262  *
263  *\return       MatcherProp * Pointer to newly allocated matcher structure
264  */
265 MatcherProp *matcherprop_copy(const MatcherProp *src)
266 {
267         MatcherProp *prop = g_new0(MatcherProp, 1);
268         
269         prop->criteria = src->criteria;
270         prop->header = src->header ? g_strdup(src->header) : NULL;
271         prop->expr = src->expr ? g_strdup(src->expr) : NULL;
272         prop->matchtype = src->matchtype;
273         
274         prop->preg = NULL; /* will be re-evaluated */
275         prop->value = src->value;
276         prop->error = src->error;       
277         return prop;            
278 }
279
280 /* ************** match ******************************/
281
282 static gboolean match_with_addresses_in_addressbook
283         (MatcherProp *prop, const gchar *str, gint type)
284 {
285         GSList *address_list = NULL;
286         GSList *walk;
287         gboolean res = FALSE;
288
289         if (str == NULL || *str == 0) 
290                 return FALSE;
291         
292         /* XXX: perhaps complete with comments too */
293         address_list = address_list_append(address_list, str);
294         if (!address_list) 
295                 return FALSE;
296
297         start_address_completion();             
298         res = FALSE;
299         for (walk = address_list; walk != NULL; walk = walk->next) {
300                 gboolean found = complete_address(walk->data) ? TRUE : FALSE;
301                 
302                 g_free(walk->data);
303                 if (!found && type == MATCHTYPE_ALL_IN_ADDRESSBOOK) {
304                         res = FALSE;
305                         break;
306                 } else if (found) 
307                         res = TRUE;
308         }
309
310         g_slist_free(address_list);
311
312         end_address_completion();
313         
314         return res;
315 }
316
317 /*!
318  *\brief        Find out if a string matches a condition
319  *
320  *\param        prop Matcher structure
321  *\param        str String to check 
322  *
323  *\return       gboolean TRUE if str matches the condition in the 
324  *              matcher structure
325  */
326 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str)
327 {
328         gchar *str1;
329         gchar *str2;
330
331         if (str == NULL)
332                 return FALSE;
333
334         switch (prop->matchtype) {
335         case MATCHTYPE_REGEXPCASE:
336         case MATCHTYPE_REGEXP:
337                 if (!prop->preg && (prop->error == 0)) {
338                         prop->preg = g_new0(regex_t, 1);
339                         /* if regexp then don't use the escaped string */
340                         if (regcomp(prop->preg, prop->expr,
341                                     REG_NOSUB | REG_EXTENDED
342                                     | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
343                                     ? REG_ICASE : 0)) != 0) {
344                                 prop->error = 1;
345                                 g_free(prop->preg);
346                                 prop->preg = NULL;
347                         }
348                 }
349                 if (prop->preg == NULL)
350                         return FALSE;
351                 
352                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
353                         return TRUE;
354                 else
355                         return FALSE;
356                         
357         case MATCHTYPE_ALL_IN_ADDRESSBOOK:      
358         case MATCHTYPE_ANY_IN_ADDRESSBOOK:
359                 return match_with_addresses_in_addressbook
360                         (prop, str, prop->matchtype);
361
362         case MATCHTYPE_MATCH:
363                 return (strstr(str, prop->expr) != NULL);
364
365         /* FIXME: put upper in unesc_str */
366         case MATCHTYPE_MATCHCASE:
367                 str2 = alloca(strlen(prop->expr) + 1);
368                 strcpy(str2, prop->expr);
369                 g_strup(str2);
370                 str1 = alloca(strlen(str) + 1);
371                 strcpy(str1, str);
372                 g_strup(str1);
373                 return (strstr(str1, str2) != NULL);
374                 
375         default:
376                 return FALSE;
377         }
378 }
379
380 /* FIXME body search is a hack. */
381 static gboolean matcherprop_string_decode_match(MatcherProp *prop, const gchar *str)
382 {
383         gchar *utf = NULL;
384         gchar tmp[BUFFSIZE];
385         gboolean res = FALSE;
386
387         if (str == NULL)
388                 return FALSE;
389
390         /* we try to decode QP first, because it's faster than base64 */
391         qp_decode_const(tmp, BUFFSIZE-1, str);
392         if (!g_utf8_validate(tmp, -1, NULL)) {
393                 utf = conv_codeset_strdup
394                         (tmp, conv_get_locale_charset_str_no_utf8(),
395                          CS_INTERNAL);
396                 res = matcherprop_string_match(prop, utf);
397                 g_free(utf);
398         } else {
399                 res = matcherprop_string_match(prop, tmp);
400         }
401         
402         if (res == FALSE && (strchr(prop->expr, '=') || strchr(prop->expr, '_')) ) {
403                 /* if searching for something with an equal char, maybe 
404                  * we should try to match the non-decoded string. 
405                  * In case it was not qp-encoded. */
406                 if (!g_utf8_validate(str, -1, NULL)) {
407                         utf = conv_codeset_strdup
408                                 (str, conv_get_locale_charset_str_no_utf8(),
409                                  CS_INTERNAL);
410                         res = matcherprop_string_match(prop, utf);
411                 } else {
412                         res = matcherprop_string_match(prop, str);
413                 }
414         }
415
416         /* FIXME base64 decoding is too slow, especially since text can 
417          * easily be handled as base64. Don't even try now. */
418
419         return res;
420 }
421
422 #ifdef USE_PTHREAD
423 typedef struct _thread_data {
424         const gchar *cmd;
425         gboolean done;
426 } thread_data;
427 #endif
428
429 #ifdef USE_PTHREAD
430 void *matcher_test_thread(void *data)
431 {
432         thread_data *td = (thread_data *)data;
433         int result = -1;
434
435         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
436         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
437
438         result = system(td->cmd);
439         if (result) perror("system");
440         td->done = TRUE; /* let the caller thread join() */
441         return GINT_TO_POINTER(result);
442 }
443 #endif
444
445 /*!
446  *\brief        Execute a command defined in the matcher structure
447  *
448  *\param        prop Pointer to matcher structure
449  *\param        info Pointer to message info structure
450  *
451  *\return       gboolean TRUE if command was executed succesfully
452  */
453 static gboolean matcherprop_match_test(const MatcherProp *prop, 
454                                           MsgInfo *info)
455 {
456         gchar *file;
457         gchar *cmd;
458         gint retval;
459 #ifdef USE_PTHREAD
460         pthread_t pt;
461         thread_data *td = g_new0(thread_data, 1);
462         void *res = NULL;
463         time_t start_time = time(NULL);
464 #endif
465
466         file = procmsg_get_message_file(info);
467         if (file == NULL)
468                 return FALSE;
469         g_free(file);           
470
471         cmd = matching_build_command(prop->expr, info);
472         if (cmd == NULL)
473                 return FALSE;
474
475 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
476         td->cmd = cmd;
477         td->done = FALSE;
478         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, 
479                         matcher_test_thread, td) != 0)
480                 retval = system(cmd);
481         else {
482                 printf("waiting for test thread\n");
483                 while(!td->done) {
484                         /* don't let the interface freeze while waiting */
485                         sylpheed_do_idle();
486                         if (time(NULL) - start_time > 30) {
487                                 pthread_cancel(pt);
488                                 td->done = TRUE;
489                                 retval = -1;
490                         }
491                 }
492                 pthread_join(pt, &res);
493                 retval = GPOINTER_TO_INT(res);
494                 printf(" test thread returned %d\n", retval);
495         }
496         g_free(td);
497 #else
498         retval = system(cmd);
499 #endif
500         debug_print("Command exit code: %d\n", retval);
501
502         g_free(cmd);
503         return (retval == 0);
504 }
505
506 /*!
507  *\brief        Check if a message matches the condition in a matcher
508  *              structure.
509  *
510  *\param        prop Pointer to matcher structure
511  *\param        info Pointer to message info
512  *
513  *\return       gboolean TRUE if a match
514  */
515 gboolean matcherprop_match(MatcherProp *prop, 
516                            MsgInfo *info)
517 {
518         time_t t;
519
520         switch(prop->criteria) {
521         case MATCHCRITERIA_ALL:
522                 return 1;
523         case MATCHCRITERIA_UNREAD:
524                 return MSG_IS_UNREAD(info->flags);
525         case MATCHCRITERIA_NOT_UNREAD:
526                 return !MSG_IS_UNREAD(info->flags);
527         case MATCHCRITERIA_NEW:
528                 return MSG_IS_NEW(info->flags);
529         case MATCHCRITERIA_NOT_NEW:
530                 return !MSG_IS_NEW(info->flags);
531         case MATCHCRITERIA_MARKED:
532                 return MSG_IS_MARKED(info->flags);
533         case MATCHCRITERIA_NOT_MARKED:
534                 return !MSG_IS_MARKED(info->flags);
535         case MATCHCRITERIA_DELETED:
536                 return MSG_IS_DELETED(info->flags);
537         case MATCHCRITERIA_NOT_DELETED:
538                 return !MSG_IS_DELETED(info->flags);
539         case MATCHCRITERIA_REPLIED:
540                 return MSG_IS_REPLIED(info->flags);
541         case MATCHCRITERIA_NOT_REPLIED:
542                 return !MSG_IS_REPLIED(info->flags);
543         case MATCHCRITERIA_FORWARDED:
544                 return MSG_IS_FORWARDED(info->flags);
545         case MATCHCRITERIA_NOT_FORWARDED:
546                 return !MSG_IS_FORWARDED(info->flags);
547         case MATCHCRITERIA_LOCKED:
548                 return MSG_IS_LOCKED(info->flags);
549         case MATCHCRITERIA_NOT_LOCKED:
550                 return !MSG_IS_LOCKED(info->flags);
551         case MATCHCRITERIA_COLORLABEL:
552                 return MSG_GET_COLORLABEL_VALUE(info->flags) == prop->value; 
553         case MATCHCRITERIA_NOT_COLORLABEL:
554                 return MSG_GET_COLORLABEL_VALUE(info->flags) != prop->value;
555         case MATCHCRITERIA_IGNORE_THREAD:
556                 return MSG_IS_IGNORE_THREAD(info->flags);
557         case MATCHCRITERIA_NOT_IGNORE_THREAD:
558                 return !MSG_IS_IGNORE_THREAD(info->flags);
559         case MATCHCRITERIA_SUBJECT:
560                 return matcherprop_string_match(prop, info->subject);
561         case MATCHCRITERIA_NOT_SUBJECT:
562                 return !matcherprop_string_match(prop, info->subject);
563         case MATCHCRITERIA_FROM:
564                 return matcherprop_string_match(prop, info->from);
565         case MATCHCRITERIA_NOT_FROM:
566                 return !matcherprop_string_match(prop, info->from);
567         case MATCHCRITERIA_TO:
568                 return matcherprop_string_match(prop, info->to);
569         case MATCHCRITERIA_NOT_TO:
570                 return !matcherprop_string_match(prop, info->to);
571         case MATCHCRITERIA_CC:
572                 return matcherprop_string_match(prop, info->cc);
573         case MATCHCRITERIA_NOT_CC:
574                 return !matcherprop_string_match(prop, info->cc);
575         case MATCHCRITERIA_TO_OR_CC:
576                 return matcherprop_string_match(prop, info->to)
577                         || matcherprop_string_match(prop, info->cc);
578         case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
579                 return !(matcherprop_string_match(prop, info->to)
580                 || matcherprop_string_match(prop, info->cc));
581         case MATCHCRITERIA_AGE_GREATER:
582                 t = time(NULL);
583                 return ((t - info->date_t) / (60 * 60 * 24)) > prop->value;
584         case MATCHCRITERIA_AGE_LOWER:
585                 t = time(NULL);
586                 return ((t - info->date_t) / (60 * 60 * 24)) < prop->value;
587         case MATCHCRITERIA_SCORE_GREATER:
588                 return info->score > prop->value;
589         case MATCHCRITERIA_SCORE_LOWER:
590                 return info->score < prop->value;
591         case MATCHCRITERIA_SCORE_EQUAL:
592                 return info->score == prop->value;
593         case MATCHCRITERIA_SIZE_GREATER:
594                 /* FIXME: info->size is an off_t */
595                 return info->size > (off_t) prop->value;
596         case MATCHCRITERIA_SIZE_EQUAL:
597                 /* FIXME: info->size is an off_t */
598                 return info->size == (off_t) prop->value;
599         case MATCHCRITERIA_SIZE_SMALLER:
600                 /* FIXME: info->size is an off_t */
601                 return info->size <  (off_t) prop->value;
602         case MATCHCRITERIA_PARTIAL:
603                 /* FIXME: info->size is an off_t */
604                 return (info->total_size != 0 && info->size != (off_t)info->total_size);
605         case MATCHCRITERIA_NOT_PARTIAL:
606                 /* FIXME: info->size is an off_t */
607                 return (info->total_size == 0 || info->size == (off_t)info->total_size);
608         case MATCHCRITERIA_NEWSGROUPS:
609                 return matcherprop_string_match(prop, info->newsgroups);
610         case MATCHCRITERIA_NOT_NEWSGROUPS:
611                 return !matcherprop_string_match(prop, info->newsgroups);
612         case MATCHCRITERIA_INREPLYTO:
613                 return matcherprop_string_match(prop, info->inreplyto);
614         case MATCHCRITERIA_NOT_INREPLYTO:
615                 return !matcherprop_string_match(prop, info->inreplyto);
616         /* FIXME: Using inreplyto, but matching the (newly implemented)
617          * list of references is better */
618         case MATCHCRITERIA_REFERENCES:
619                 return matcherprop_string_match(prop, info->inreplyto);
620         case MATCHCRITERIA_NOT_REFERENCES:
621                 return !matcherprop_string_match(prop, info->inreplyto);
622         case MATCHCRITERIA_TEST:
623                 return matcherprop_match_test(prop, info);
624         case MATCHCRITERIA_NOT_TEST:
625                 return !matcherprop_match_test(prop, info);
626         default:
627                 return 0;
628         }
629 }
630
631 /* ********************* MatcherList *************************** */
632
633 /*!
634  *\brief        Create a new list of matchers 
635  *
636  *\param        matchers List of matcher structures
637  *\param        bool_and Operator
638  *
639  *\return       MatcherList * New list
640  */
641 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
642 {
643         MatcherList *cond;
644
645         cond = g_new0(MatcherList, 1);
646
647         cond->matchers = matchers;
648         cond->bool_and = bool_and;
649
650         return cond;
651 }
652
653 /*!
654  *\brief        Frees a list of matchers
655  *
656  *\param        cond List of matchers
657  */
658 void matcherlist_free(MatcherList *cond)
659 {
660         GSList *l;
661
662         g_return_if_fail(cond);
663         for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
664                 matcherprop_free((MatcherProp *) l->data);
665         }
666         g_free(cond);
667 }
668
669 /*!
670  *\brief        Skip all headers in a message file
671  *
672  *\param        fp Message file
673  */
674 static void matcherlist_skip_headers(FILE *fp)
675 {
676         gchar buf[BUFFSIZE];
677
678         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
679                 ;
680 }
681
682 /*!
683  *\brief        Check if a header matches a matcher condition
684  *
685  *\param        matcher Matcher structure to check header for
686  *\param        buf Header name
687  *
688  *\return       boolean TRUE if matching header
689  */
690 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
691                                              gchar *buf)
692 {
693         gboolean result;
694         Header *header;
695
696         switch (matcher->criteria) {
697         case MATCHCRITERIA_HEADER:
698         case MATCHCRITERIA_NOT_HEADER:
699                 header = procheader_parse_header(buf);
700                 if (!header)
701                         return FALSE;
702                 if (procheader_headername_equal(header->name,
703                                                 matcher->header)) {
704                         if (matcher->criteria == MATCHCRITERIA_HEADER)
705                                 result = matcherprop_string_match(matcher, header->body);
706                         else
707                                 result = !matcherprop_string_match(matcher, header->body);
708                         procheader_header_free(header);
709                         return result;
710                 }
711                 else {
712                         procheader_header_free(header);
713                 }
714                 break;
715         case MATCHCRITERIA_HEADERS_PART:
716                 return matcherprop_string_match(matcher, buf);
717         case MATCHCRITERIA_MESSAGE:
718                 return matcherprop_string_decode_match(matcher, buf);
719         case MATCHCRITERIA_NOT_MESSAGE:
720                 return !matcherprop_string_decode_match(matcher, buf);
721         case MATCHCRITERIA_NOT_HEADERS_PART:
722                 return !matcherprop_string_match(matcher, buf);
723         }
724         return FALSE;
725 }
726
727 /*!
728  *\brief        Check if the matcher structure wants headers to
729  *              be matched
730  *
731  *\param        matcher Matcher structure
732  *
733  *\return       gboolean TRUE if the matcher structure describes
734  *              a header match condition
735  */
736 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
737 {
738         switch (matcher->criteria) {
739         case MATCHCRITERIA_HEADER:
740         case MATCHCRITERIA_NOT_HEADER:
741         case MATCHCRITERIA_HEADERS_PART:
742         case MATCHCRITERIA_NOT_HEADERS_PART:
743                 return TRUE;
744         default:
745                 return FALSE;
746         }
747 }
748
749 /*!
750  *\brief        Check if the matcher structure wants the message
751  *              to be matched (just perform an action on any
752  *              message)
753  *
754  *\param        matcher Matcher structure
755  *
756  *\return       gboolean TRUE if matcher condition should match
757  *              a message
758  */
759 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
760 {
761         switch (matcher->criteria) {
762         case MATCHCRITERIA_MESSAGE:
763         case MATCHCRITERIA_NOT_MESSAGE:
764                 return TRUE;
765         default:
766                 return FALSE;
767         }
768 }
769
770 /*!
771  *\brief        Check if a list of conditions matches one header in
772  *              a message file.
773  *
774  *\param        matchers List of conditions
775  *\param        fp Message file
776  *
777  *\return       gboolean TRUE if one of the headers is matched by
778  *              the list of conditions. 
779  */
780 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
781 {
782         GSList *l;
783         gchar buf[BUFFSIZE];
784
785         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
786                 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
787                         MatcherProp *matcher = (MatcherProp *) l->data;
788
789                         if (matcher->done)
790                                 continue;
791
792                         /* if the criteria is ~headers_part or ~message, ZERO lines
793                          * must NOT match for the rule to match. */
794                         if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
795                             matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
796                                 if (matcherprop_match_one_header(matcher, buf)) {
797                                         matcher->result = TRUE;
798                                 } else {
799                                         matcher->result = FALSE;
800                                         matcher->done = TRUE;
801                                 }
802                         /* else, just one line matching is enough for the rule to match
803                          */
804                         } else if (matcherprop_criteria_headers(matcher) ||
805                                    matcherprop_criteria_message(matcher)){
806                                 if (matcherprop_match_one_header(matcher, buf)) {
807                                         matcher->result = TRUE;
808                                         matcher->done = TRUE;
809                                 }
810                         }
811                         
812                         /* if the rule matched and the matchers are OR, no need to
813                          * check the others */
814                         if (matcher->result && matcher->done) {
815                                 if (!matchers->bool_and)
816                                         return TRUE;
817                         }
818                 }
819         }
820         return FALSE;
821 }
822
823 /*!
824  *\brief        Check if a matcher wants to check the message body
825  *
826  *\param        matcher Matcher structure
827  *
828  *\return       gboolean TRUE if body must be matched.
829  */
830 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
831 {
832         switch (matcher->criteria) {
833         case MATCHCRITERIA_BODY_PART:
834         case MATCHCRITERIA_NOT_BODY_PART:
835                 return TRUE;
836         default:
837                 return FALSE;
838         }
839 }
840
841 /*!
842  *\brief        Check if a (line) string matches the criteria
843  *              described by a matcher structure
844  *
845  *\param        matcher Matcher structure
846  *\param        line String
847  *
848  *\return       gboolean TRUE if string matches criteria
849  */
850 static gboolean matcherprop_match_line(MatcherProp *matcher, const gchar *line)
851 {
852         switch (matcher->criteria) {
853         case MATCHCRITERIA_BODY_PART:
854         case MATCHCRITERIA_MESSAGE:
855                 return matcherprop_string_decode_match(matcher, line);
856         case MATCHCRITERIA_NOT_BODY_PART:
857         case MATCHCRITERIA_NOT_MESSAGE:
858                 return !matcherprop_string_decode_match(matcher, line);
859         }
860         return FALSE;
861 }
862
863 /*!
864  *\brief        Check if a line in a message file's body matches
865  *              the criteria
866  *
867  *\param        matchers List of conditions
868  *\param        fp Message file
869  *
870  *\return       gboolean TRUE if succesful match
871  */
872 static gboolean matcherlist_match_body(MatcherList *matchers, FILE *fp)
873 {
874         GSList *l;
875         gchar buf[BUFFSIZE];
876         
877         while (fgets(buf, sizeof(buf), fp) != NULL) {
878                 for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
879                         MatcherProp *matcher = (MatcherProp *) l->data;
880                         
881                         if (matcher->done) 
882                                 continue;
883
884                         /* if the criteria is ~body_part or ~message, ZERO lines
885                          * must NOT match for the rule to match. */
886                         if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
887                             matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
888                                 if (matcherprop_match_line(matcher, buf)) {
889                                         matcher->result = TRUE;
890                                 } else {
891                                         matcher->result = FALSE;
892                                         matcher->done = TRUE;
893                                 }
894                         /* else, just one line has to match */
895                         } else if (matcherprop_criteria_body(matcher) ||
896                                    matcherprop_criteria_message(matcher)) {
897                                 if (matcherprop_match_line(matcher, buf)) {
898                                         matcher->result = TRUE;
899                                         matcher->done = TRUE;
900                                 }
901                         }
902
903                         /* if the matchers are OR'ed and the rule matched,
904                          * no need to check the others. */
905                         if (matcher->result && matcher->done) {
906                                 if (!matchers->bool_and)
907                                         return TRUE;
908                         }
909                 }
910         }
911         return FALSE;
912 }
913
914 /*!
915  *\brief        Check if a message file matches criteria
916  *
917  *\param        matchers Criteria
918  *\param        info Message info
919  *\param        result Default result
920  *
921  *\return       gboolean TRUE if matched
922  */
923 gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
924                                 gboolean result)
925 {
926         gboolean read_headers;
927         gboolean read_body;
928         GSList *l;
929         FILE *fp;
930         gchar *file;
931
932         /* file need to be read ? */
933
934         read_headers = FALSE;
935         read_body = FALSE;
936         for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
937                 MatcherProp *matcher = (MatcherProp *) l->data;
938
939                 if (matcherprop_criteria_headers(matcher))
940                         read_headers = TRUE;
941                 if (matcherprop_criteria_body(matcher))
942                         read_body = TRUE;
943                 if (matcherprop_criteria_message(matcher)) {
944                         read_headers = TRUE;
945                         read_body = TRUE;
946                 }
947                 matcher->result = FALSE;
948                 matcher->done = FALSE;
949         }
950
951         if (!read_headers && !read_body)
952                 return result;
953
954         file = procmsg_get_message_file_full(info, read_headers, read_body);
955         if (file == NULL)
956                 return FALSE;
957
958         if ((fp = g_fopen(file, "rb")) == NULL) {
959                 FILE_OP_ERROR(file, "fopen");
960                 g_free(file);
961                 return result;
962         }
963
964         /* read the headers */
965
966         if (read_headers) {
967                 if (matcherlist_match_headers(matchers, fp))
968                         read_body = FALSE;
969         } else {
970                 matcherlist_skip_headers(fp);
971         }
972
973         /* read the body */
974         if (read_body) {
975                 matcherlist_match_body(matchers, fp);
976         }
977         
978         for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
979                 MatcherProp *matcher = (MatcherProp *) l->data;
980
981                 if (matcherprop_criteria_headers(matcher) ||
982                     matcherprop_criteria_body(matcher)    ||
983                     matcherprop_criteria_message(matcher)) {
984                         if (matcher->result) {
985                                 if (!matchers->bool_and) {
986                                         result = TRUE;
987                                         break;
988                                 }
989                         }
990                         else {
991                                 if (matchers->bool_and) {
992                                         result = FALSE;
993                                         break;
994                                 }
995                         }
996                 }                       
997         }
998
999         g_free(file);
1000
1001         fclose(fp);
1002         
1003         return result;
1004 }
1005
1006 /*!
1007  *\brief        Test list of conditions on a message.
1008  *
1009  *\param        matchers List of conditions
1010  *\param        info Message info
1011  *
1012  *\return       gboolean TRUE if matched
1013  */
1014 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
1015 {
1016         GSList *l;
1017         gboolean result;
1018
1019         if (!matchers)
1020                 return FALSE;
1021
1022         if (matchers->bool_and)
1023                 result = TRUE;
1024         else
1025                 result = FALSE;
1026
1027         /* test the cached elements */
1028
1029         for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
1030                 MatcherProp *matcher = (MatcherProp *) l->data;
1031
1032                 switch(matcher->criteria) {
1033                 case MATCHCRITERIA_ALL:
1034                 case MATCHCRITERIA_UNREAD:
1035                 case MATCHCRITERIA_NOT_UNREAD:
1036                 case MATCHCRITERIA_NEW:
1037                 case MATCHCRITERIA_NOT_NEW:
1038                 case MATCHCRITERIA_MARKED:
1039                 case MATCHCRITERIA_NOT_MARKED:
1040                 case MATCHCRITERIA_DELETED:
1041                 case MATCHCRITERIA_NOT_DELETED:
1042                 case MATCHCRITERIA_REPLIED:
1043                 case MATCHCRITERIA_NOT_REPLIED:
1044                 case MATCHCRITERIA_FORWARDED:
1045                 case MATCHCRITERIA_NOT_FORWARDED:
1046                 case MATCHCRITERIA_LOCKED:
1047                 case MATCHCRITERIA_NOT_LOCKED:
1048                 case MATCHCRITERIA_COLORLABEL:
1049                 case MATCHCRITERIA_NOT_COLORLABEL:
1050                 case MATCHCRITERIA_IGNORE_THREAD:
1051                 case MATCHCRITERIA_NOT_IGNORE_THREAD:
1052                 case MATCHCRITERIA_SUBJECT:
1053                 case MATCHCRITERIA_NOT_SUBJECT:
1054                 case MATCHCRITERIA_FROM:
1055                 case MATCHCRITERIA_NOT_FROM:
1056                 case MATCHCRITERIA_TO:
1057                 case MATCHCRITERIA_NOT_TO:
1058                 case MATCHCRITERIA_CC:
1059                 case MATCHCRITERIA_NOT_CC:
1060                 case MATCHCRITERIA_TO_OR_CC:
1061                 case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1062                 case MATCHCRITERIA_AGE_GREATER:
1063                 case MATCHCRITERIA_AGE_LOWER:
1064                 case MATCHCRITERIA_NEWSGROUPS:
1065                 case MATCHCRITERIA_NOT_NEWSGROUPS:
1066                 case MATCHCRITERIA_INREPLYTO:
1067                 case MATCHCRITERIA_NOT_INREPLYTO:
1068                 case MATCHCRITERIA_REFERENCES:
1069                 case MATCHCRITERIA_NOT_REFERENCES:
1070                 case MATCHCRITERIA_SCORE_GREATER:
1071                 case MATCHCRITERIA_SCORE_LOWER:
1072                 case MATCHCRITERIA_SCORE_EQUAL:
1073                 case MATCHCRITERIA_SIZE_GREATER:
1074                 case MATCHCRITERIA_SIZE_SMALLER:
1075                 case MATCHCRITERIA_SIZE_EQUAL:
1076                 case MATCHCRITERIA_TEST:
1077                 case MATCHCRITERIA_NOT_TEST:
1078                 case MATCHCRITERIA_PARTIAL:
1079                 case MATCHCRITERIA_NOT_PARTIAL:
1080                         if (matcherprop_match(matcher, info)) {
1081                                 if (!matchers->bool_and) {
1082                                         return TRUE;
1083                                 }
1084                         }
1085                         else {
1086                                 if (matchers->bool_and) {
1087                                         return FALSE;
1088                                 }
1089                         }
1090                 }
1091         }
1092
1093         /* test the condition on the file */
1094
1095         if (matcherlist_match_file(matchers, info, result)) {
1096                 if (!matchers->bool_and)
1097                         return TRUE;
1098         }
1099         else {
1100                 if (matchers->bool_and)
1101                         return FALSE;
1102         }
1103
1104         return result;
1105 }
1106
1107
1108 static gint quote_filter_str(gchar * result, guint size,
1109                              const gchar * path)
1110 {
1111         const gchar * p;
1112         gchar * result_p;
1113         guint remaining;
1114
1115         result_p = result;
1116         remaining = size;
1117
1118         for(p = path ; * p != '\0' ; p ++) {
1119
1120                 if ((* p != '\"') && (* p != '\\')) {
1121                         if (remaining > 0) {
1122                                 * result_p = * p;
1123                                 result_p ++; 
1124                                 remaining --;
1125                         }
1126                         else {
1127                                 result[size - 1] = '\0';
1128                                 return -1;
1129                         }
1130                 }
1131                 else { 
1132                         if (remaining >= 2) {
1133                                 * result_p = '\\';
1134                                 result_p ++; 
1135                                 * result_p = * p;
1136                                 result_p ++; 
1137                                 remaining -= 2;
1138                         }
1139                         else {
1140                                 result[size - 1] = '\0';
1141                                 return -1;
1142                         }
1143                 }
1144         }
1145         if (remaining > 0) {
1146                 * result_p = '\0';
1147         }
1148         else {
1149                 result[size - 1] = '\0';
1150                 return -1;
1151         }
1152   
1153         return 0;
1154 }
1155
1156
1157 gchar * matcher_quote_str(const gchar * src)
1158 {
1159         gchar * res;
1160         gint len;
1161         
1162         len = strlen(src) * 2 + 1;
1163         res = g_malloc(len);
1164         quote_filter_str(res, len, src);
1165         
1166         return res;
1167 }
1168
1169 /*!
1170  *\brief        Convert a matcher structure to a string
1171  *
1172  *\param        matcher Matcher structure
1173  *
1174  *\return       gchar * Newly allocated string
1175  */
1176 gchar *matcherprop_to_string(MatcherProp *matcher)
1177 {
1178         gchar *matcher_str = NULL;
1179         const gchar *criteria_str;
1180         const gchar *matchtype_str;
1181         int i;
1182         gchar * quoted_expr;
1183         
1184         criteria_str = NULL;
1185         for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
1186                 if (matchparser_tab[i].id == matcher->criteria)
1187                         criteria_str = matchparser_tab[i].str;
1188         }
1189         if (criteria_str == NULL)
1190                 return NULL;
1191
1192         switch (matcher->criteria) {
1193         case MATCHCRITERIA_AGE_GREATER:
1194         case MATCHCRITERIA_AGE_LOWER:
1195         case MATCHCRITERIA_SCORE_GREATER:
1196         case MATCHCRITERIA_SCORE_LOWER:
1197         case MATCHCRITERIA_SCORE_EQUAL:
1198         case MATCHCRITERIA_SIZE_GREATER:
1199         case MATCHCRITERIA_SIZE_SMALLER:
1200         case MATCHCRITERIA_SIZE_EQUAL:
1201         case MATCHCRITERIA_COLORLABEL:
1202         case MATCHCRITERIA_NOT_COLORLABEL:
1203                 return g_strdup_printf("%s %i", criteria_str, matcher->value);
1204         case MATCHCRITERIA_ALL:
1205         case MATCHCRITERIA_UNREAD:
1206         case MATCHCRITERIA_NOT_UNREAD:
1207         case MATCHCRITERIA_NEW:
1208         case MATCHCRITERIA_NOT_NEW:
1209         case MATCHCRITERIA_MARKED:
1210         case MATCHCRITERIA_NOT_MARKED:
1211         case MATCHCRITERIA_DELETED:
1212         case MATCHCRITERIA_NOT_DELETED:
1213         case MATCHCRITERIA_REPLIED:
1214         case MATCHCRITERIA_NOT_REPLIED:
1215         case MATCHCRITERIA_FORWARDED:
1216         case MATCHCRITERIA_NOT_FORWARDED:
1217         case MATCHCRITERIA_LOCKED:
1218         case MATCHCRITERIA_NOT_LOCKED:
1219         case MATCHCRITERIA_PARTIAL:
1220         case MATCHCRITERIA_NOT_PARTIAL:
1221         case MATCHCRITERIA_IGNORE_THREAD:
1222         case MATCHCRITERIA_NOT_IGNORE_THREAD:
1223                 return g_strdup(criteria_str);
1224         case MATCHCRITERIA_TEST:
1225         case MATCHCRITERIA_NOT_TEST:
1226                 quoted_expr = matcher_quote_str(matcher->expr);
1227                 matcher_str = g_strdup_printf("%s \"%s\"",
1228                                               criteria_str, quoted_expr);
1229                 g_free(quoted_expr);
1230                 return matcher_str;
1231         }
1232
1233         matchtype_str = NULL;
1234         for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
1235                 if (matchparser_tab[i].id == matcher->matchtype)
1236                         matchtype_str = matchparser_tab[i].str;
1237         }
1238
1239         if (matchtype_str == NULL)
1240                 return NULL;
1241
1242         switch (matcher->matchtype) {
1243         case MATCHTYPE_MATCH:
1244         case MATCHTYPE_MATCHCASE:
1245         case MATCHTYPE_REGEXP:
1246         case MATCHTYPE_REGEXPCASE:
1247         case MATCHTYPE_ALL_IN_ADDRESSBOOK:
1248         case MATCHTYPE_ANY_IN_ADDRESSBOOK:
1249                 quoted_expr = matcher_quote_str(matcher->expr);
1250                 if (matcher->header) {
1251                         gchar * quoted_header;
1252                         
1253                         quoted_header = matcher_quote_str(matcher->header);
1254                         matcher_str = g_strdup_printf
1255                                         ("%s \"%s\" %s \"%s\"",
1256                                          criteria_str, quoted_header,
1257                                          matchtype_str, quoted_expr);
1258                         g_free(quoted_header);
1259                 }
1260                 else
1261                         matcher_str = g_strdup_printf
1262                                         ("%s %s \"%s\"", criteria_str,
1263                                          matchtype_str, quoted_expr);
1264                 g_free(quoted_expr);
1265                 break;
1266         }
1267
1268         return matcher_str;
1269 }
1270
1271 /*!
1272  *\brief        Convert a list of conditions to a string
1273  *
1274  *\param        matchers List of conditions
1275  *
1276  *\return       gchar * Newly allocated string
1277  */
1278 gchar *matcherlist_to_string(const MatcherList *matchers)
1279 {
1280         gint count;
1281         gchar **vstr;
1282         GSList *l;
1283         gchar **cur_str;
1284         gchar *result = NULL;
1285
1286         count = g_slist_length(matchers->matchers);
1287         vstr = g_new(gchar *, count + 1);
1288
1289         for (l = matchers->matchers, cur_str = vstr; l != NULL;
1290              l = g_slist_next(l), cur_str ++) {
1291                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1292                 if (*cur_str == NULL)
1293                         break;
1294         }
1295         *cur_str = NULL;
1296         
1297         if (matchers->bool_and)
1298                 result = g_strjoinv(" & ", vstr);
1299         else
1300                 result = g_strjoinv(" | ", vstr);
1301
1302         for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1303                 g_free(*cur_str);
1304         g_free(vstr);
1305
1306         return result;
1307 }
1308
1309
1310 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
1311 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
1312
1313 static void add_str_default(gchar ** dest,
1314                             const gchar * s, const gchar * d)
1315 {
1316         gchar quoted_str[4096];
1317         const gchar * str;
1318         
1319         if (s != NULL)
1320                 str = s;
1321         else
1322                 str = d;
1323         
1324         quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
1325         strcpy(* dest, quoted_str);
1326         
1327         (* dest) += strlen(* dest);
1328 }
1329
1330 /* matching_build_command() - preferably cmd should be unescaped */
1331 /*!
1332  *\brief        Build the command line to execute
1333  *
1334  *\param        cmd String with command line specifiers
1335  *\param        info Message info to use for command
1336  *
1337  *\return       gchar * Newly allocated string
1338  */
1339 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
1340 {
1341         const gchar *s = cmd;
1342         gchar *filename = NULL;
1343         gchar *processed_cmd;
1344         gchar *p;
1345         gint size;
1346
1347         const gchar *const no_subject    = _("(none)") ;
1348         const gchar *const no_from       = _("(none)") ;
1349         const gchar *const no_to         = _("(none)") ;
1350         const gchar *const no_cc         = _("(none)") ;
1351         const gchar *const no_date       = _("(none)") ;
1352         const gchar *const no_msgid      = _("(none)") ;
1353         const gchar *const no_newsgroups = _("(none)") ;
1354         const gchar *const no_references = _("(none)") ;
1355
1356         size = STRLEN_ZERO(cmd) + 1;
1357         while (*s != '\0') {
1358                 if (*s == '%') {
1359                         s++;
1360                         switch (*s) {
1361                         case '%':
1362                                 size -= 1;
1363                                 break;
1364                         case 's': /* subject */
1365                                 size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
1366                                 break;
1367                         case 'f': /* from */
1368                                 size += STRLEN_DEFAULT(info->from, no_from) - 2;
1369                                 break;
1370                         case 't': /* to */
1371                                 size += STRLEN_DEFAULT(info->to, no_to) - 2;
1372                                 break;
1373                         case 'c': /* cc */
1374                                 size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
1375                                 break;
1376                         case 'd': /* date */
1377                                 size += STRLEN_DEFAULT(info->date, no_date) - 2;
1378                                 break;
1379                         case 'i': /* message-id */
1380                                 size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
1381                                 break;
1382                         case 'n': /* newsgroups */
1383                                 size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
1384                                 break;
1385                         case 'r': /* references */
1386                                 /* FIXME: using the inreplyto header for reference */
1387                                 size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
1388                                 break;
1389                         case 'F': /* file */
1390                                 if (filename == NULL)
1391                                         filename = folder_item_fetch_msg(info->folder, info->msgnum);
1392                                 
1393                                 if (filename == NULL) {
1394                                         g_warning("filename is not set");
1395                                         return NULL;
1396                                 }
1397                                 else {
1398                                         size += strlen(filename) - 2;
1399                                 }
1400                                 break;
1401                         }
1402                         s++;
1403                 }
1404                 else s++;
1405         }
1406         
1407         /* as the string can be quoted, we double the result */
1408         size *= 2;
1409
1410         processed_cmd = g_new0(gchar, size);
1411         s = cmd;
1412         p = processed_cmd;
1413
1414         while (*s != '\0') {
1415                 if (*s == '%') {
1416                         s++;
1417                         switch (*s) {
1418                         case '%':
1419                                 *p = '%';
1420                                 p++;
1421                                 break;
1422                         case 's': /* subject */
1423                                 add_str_default(&p, info->subject,
1424                                                 no_subject);
1425                                 break;
1426                         case 'f': /* from */
1427                                 add_str_default(&p, info->from,
1428                                                 no_from);
1429                                 break;
1430                         case 't': /* to */
1431                                 add_str_default(&p, info->to,
1432                                                 no_to);
1433                                 break;
1434                         case 'c': /* cc */
1435                                 add_str_default(&p, info->cc,
1436                                                 no_cc);
1437                                 break;
1438                         case 'd': /* date */
1439                                 add_str_default(&p, info->date,
1440                                                 no_date);
1441                                 break;
1442                         case 'i': /* message-id */
1443                                 add_str_default(&p, info->msgid,
1444                                                 no_msgid);
1445                                 break;
1446                         case 'n': /* newsgroups */
1447                                 add_str_default(&p, info->newsgroups,
1448                                                 no_newsgroups);
1449                                 break;
1450                         case 'r': /* references */
1451                                 /* FIXME: using the inreplyto header for references */
1452                                 add_str_default(&p, info->inreplyto, no_references);
1453                                 break;
1454                         case 'F': /* file */
1455                                 if (filename != NULL)
1456                                         add_str_default(&p, filename, NULL);
1457                                 break;
1458                         default:
1459                                 *p = '%';
1460                                 p++;
1461                                 *p = *s;
1462                                 p++;
1463                                 break;
1464                         }
1465                         s++;
1466                 }
1467                 else {
1468                         *p = *s;
1469                         p++;
1470                         s++;
1471                 }
1472         }
1473         g_free(filename);
1474         
1475         return processed_cmd;
1476 }
1477 #undef STRLEN_DEFAULT
1478 #undef STRLEN_ZERO
1479
1480 /* ************************************************************ */
1481
1482
1483 /*!
1484  *\brief        Write filtering list to file
1485  *
1486  *\param        fp File
1487  *\param        prefs_filtering List of filtering conditions
1488  */
1489 static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
1490 {
1491         GSList *cur = NULL;
1492
1493         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
1494                 gchar *filtering_str = NULL;
1495                 gchar *tmp_name = NULL;
1496                 FilteringProp *prop = NULL;
1497
1498                 if (NULL == (prop = (FilteringProp *) cur->data))
1499                         continue;
1500                 
1501                 if (NULL == (filtering_str = filteringprop_to_string(prop)))
1502                         continue;
1503
1504                 if (prop->enabled) {
1505                         if (fputs("enabled ", fp) == EOF) {
1506                                 FILE_OP_ERROR("filtering config", "fputs");
1507                                 return;
1508                         }
1509                 } else {
1510                         if (fputs("disabled ", fp) == EOF) {
1511                                 FILE_OP_ERROR("filtering config", "fputs");
1512                                 return;
1513                         }
1514                 }
1515
1516                 if (fputs("rulename \"", fp) == EOF) {
1517                         FILE_OP_ERROR("filtering config", "fputs");
1518                         g_free(filtering_str);
1519                         return;
1520                 }
1521                 tmp_name = prop->name;
1522                 while (tmp_name && *tmp_name != '\0') {
1523                         if (*tmp_name != '"') {
1524                                 if (fputc(*tmp_name, fp) == EOF) {
1525                                         FILE_OP_ERROR("filtering config", "fputs || fputc");
1526                                         g_free(filtering_str);
1527                                         return;
1528                                 }
1529                         } else if (*tmp_name == '"') {
1530                                 if (fputc('\\', fp) == EOF ||
1531                                     fputc('"', fp) == EOF) {
1532                                         FILE_OP_ERROR("filtering config", "fputs || fputc");
1533                                         g_free(filtering_str);
1534                                         return;
1535                                 }
1536                         }
1537                         tmp_name ++;
1538                 }
1539                 if (fputs("\" ", fp) == EOF) {
1540                         FILE_OP_ERROR("filtering config", "fputs");
1541                         g_free(filtering_str);
1542                         return;
1543                 }
1544
1545                 if (prop->account_id != 0) {
1546                         gchar *tmp = NULL;
1547
1548                         tmp = g_strdup_printf("account %d ", prop->account_id);
1549                         if (fputs(tmp, fp) == EOF) {
1550                                 FILE_OP_ERROR("filtering config", "fputs");
1551                                 g_free(tmp);
1552                                 return;
1553                         }
1554                         g_free(tmp);
1555                 }
1556
1557                 if(fputs(filtering_str, fp) == EOF ||
1558                     fputc('\n', fp) == EOF) {
1559                         FILE_OP_ERROR("filtering config", "fputs || fputc");
1560                         g_free(filtering_str);
1561                         return;
1562                 }
1563                 g_free(filtering_str);
1564         }
1565 }
1566
1567 /*!
1568  *\brief        Write matchers from a folder item
1569  *
1570  *\param        node Node with folder info
1571  *\param        data File pointer
1572  *
1573  *\return       gboolean FALSE
1574  */
1575 static gboolean prefs_matcher_write_func(GNode *node, gpointer data)
1576 {
1577         FolderItem *item;
1578         FILE *fp = data;
1579         gchar *id;
1580         GSList *prefs_filtering;
1581
1582         item = node->data;
1583         /* prevent warning */
1584         if (item->path == NULL)
1585                 return FALSE;
1586         id = folder_item_get_identifier(item);
1587         if (id == NULL)
1588                 return FALSE;
1589         prefs_filtering = item->prefs->processing;
1590
1591         if (prefs_filtering != NULL) {
1592                 fprintf(fp, "[%s]\n", id);
1593                 prefs_filtering_write(fp, prefs_filtering);
1594                 fputc('\n', fp);
1595         }
1596
1597         g_free(id);
1598
1599         return FALSE;
1600 }
1601
1602 /*!
1603  *\brief        Save matchers from folder items
1604  *
1605  *\param        fp File
1606  */
1607 static void prefs_matcher_save(FILE *fp)
1608 {
1609         GList *cur;
1610
1611         for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1612                 Folder *folder;
1613
1614                 folder = (Folder *) cur->data;
1615                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1616                                 prefs_matcher_write_func, fp);
1617         }
1618         
1619         /* pre global rules */
1620         fprintf(fp, "[preglobal]\n");
1621         prefs_filtering_write(fp, pre_global_processing);
1622         fputc('\n', fp);
1623
1624         /* post global rules */
1625         fprintf(fp, "[postglobal]\n");
1626         prefs_filtering_write(fp, post_global_processing);
1627         fputc('\n', fp);
1628         
1629         /* filtering rules */
1630         fprintf(fp, "[filtering]\n");
1631         prefs_filtering_write(fp, filtering_rules);
1632         fputc('\n', fp);
1633 }
1634
1635 /*!
1636  *\brief        Write filtering / matcher configuration file
1637  */
1638 void prefs_matcher_write_config(void)
1639 {
1640         gchar *rcpath;
1641         PrefFile *pfile;
1642
1643         debug_print("Writing matcher configuration...\n");
1644
1645         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1646                              MATCHER_RC, NULL);
1647
1648         if ((pfile = prefs_write_open(rcpath)) == NULL) {
1649                 g_warning("failed to write configuration to file\n");
1650                 g_free(rcpath);
1651                 return;
1652         }
1653
1654
1655         prefs_matcher_save(pfile->fp);
1656
1657         g_free(rcpath);
1658
1659         if (prefs_file_close(pfile) < 0) {
1660                 g_warning("failed to write configuration to file\n");
1661                 return;
1662         }
1663 }
1664
1665 /* ******************************************************************* */
1666
1667 void matcher_add_rulenames(const gchar *rcpath)
1668 {
1669         gchar *newpath = g_strconcat(rcpath, ".new", NULL);
1670         FILE *src = g_fopen(rcpath, "rb");
1671         FILE *dst = g_fopen(newpath, "wb");
1672         gchar buf[BUFFSIZE];
1673
1674         if (dst == NULL) {
1675                 perror("fopen");
1676                 g_free(newpath);
1677                 return;
1678         }
1679
1680         while (fgets (buf, sizeof(buf), src) != NULL) {
1681                 if (strlen(buf) > 2 && buf[0] != '['
1682                 && strncmp(buf, "rulename \"", 10)) {
1683                         fwrite("rulename \"\" ",
1684                                 strlen("rulename \"\" "), 1, dst);
1685                 }
1686                 fwrite(buf, strlen(buf), 1, dst);
1687         }
1688         fclose(dst);
1689         fclose(src);
1690         move_file(newpath, rcpath, TRUE);
1691         g_free(newpath);
1692 }
1693
1694 /*!
1695  *\brief        Read matcher configuration
1696  */
1697 void prefs_matcher_read_config(void)
1698 {
1699         gchar *rcpath;
1700         gchar *rc_old_format;
1701         FILE *f;
1702
1703         create_matchparser_hashtab();
1704         prefs_filtering_clear();
1705
1706         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
1707         rc_old_format = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, 
1708                                 ".pre_names", NULL);
1709         
1710         if (!is_file_exist(rc_old_format) && is_file_exist(rcpath)) {
1711                 /* backup file with no rules names, in case 
1712                  * anything goes wrong */
1713                 copy_file(rcpath, rc_old_format, FALSE);
1714                 /* now hack the file in order to have it to the new format */
1715                 matcher_add_rulenames(rcpath);
1716         }
1717         
1718         g_free(rc_old_format);
1719
1720         f = g_fopen(rcpath, "rb");
1721         g_free(rcpath);
1722
1723         if (f != NULL) {
1724                 matcher_parser_start_parsing(f);
1725                 fclose(matcher_parserin);
1726         }
1727         else {
1728                 /* previous version compatibility */
1729
1730                 /* printf("reading filtering\n"); */
1731                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1732                                      FILTERING_RC, NULL);
1733                 f = g_fopen(rcpath, "rb");
1734                 g_free(rcpath);
1735                 
1736                 if (f != NULL) {
1737                         matcher_parser_start_parsing(f);
1738                         fclose(matcher_parserin);
1739                 }
1740                 
1741                 /* printf("reading scoring\n"); */
1742                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1743                                      SCORING_RC, NULL);
1744                 f = g_fopen(rcpath, "rb");
1745                 g_free(rcpath);
1746                 
1747                 if (f != NULL) {
1748                         matcher_parser_start_parsing(f);
1749                         fclose(matcher_parserin);
1750                 }
1751         }
1752 }