dbb4922bdc5eb20a4ea9b7e3d066211d6d28f5ba
[claws.git] / src / matcher.c
1 #include <ctype.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include "utils.h"
6 #include "defs.h"
7 #include "procheader.h"
8 #include "matcher.h"
9
10 struct _MatchParser {
11         gint id;
12         gchar * str;
13 };
14
15 typedef struct _MatchParser MatchParser;
16
17 MatchParser matchparser_tab[] = {
18         /* msginfo */
19         {MATCHING_ALL, "all"},
20         {MATCHING_SUBJECT, "subject"},
21         {MATCHING_NOT_SUBJECT, "~subject"},
22         {MATCHING_FROM, "from"},
23         {MATCHING_NOT_FROM, "~from"},
24         {MATCHING_TO, "to"},
25         {MATCHING_NOT_TO, "~to"},
26         {MATCHING_CC, "cc"},
27         {MATCHING_NOT_CC, "~cc"},
28         {MATCHING_TO_OR_CC, "to_or_cc"},
29         {MATCHING_NOT_TO_AND_NOT_CC, "~to_or_cc"},
30         {MATCHING_AGE_GREATER, "age_greater"},
31         {MATCHING_AGE_LOWER, "age_lower"},
32         {MATCHING_NEWSGROUPS, "newsgroups"},
33         {MATCHING_NOT_NEWSGROUPS, "~newsgroups"},
34
35         /* content have to be read */
36         {MATCHING_HEADER, "header"},
37         {MATCHING_NOT_HEADER, "~header"},
38         {MATCHING_HEADERS_PART, "headers_part"},
39         {MATCHING_NOT_HEADERS_PART, "~headers_part"},
40         {MATCHING_MESSAGE, "message"},
41         {MATCHING_NOT_MESSAGE, "~message"},
42         {MATCHING_BODY_PART, "body_part"},
43         {MATCHING_NOT_BODY_PART, "~body_part"},
44
45         /* match type */
46         {MATCHING_MATCHCASE, "matchcase"},
47         {MATCHING_MATCH, "match"},
48         {MATCHING_REGEXPCASE, "regexpcase"},
49         {MATCHING_REGEXP, "regexp"},
50
51         /* actions */
52         {MATCHING_SCORE, "score"},
53 };
54
55 /*
56   syntax for matcher
57
58   header "x-mailing" match "toto"
59   subject match "regexp" & to regexp "regexp"
60   subject match "regexp" | to regexpcase "regexp" | age_sup 5
61  */
62
63 static gboolean matcher_is_blank(gchar ch);
64
65 /* ******************* parser *********************** */
66
67 static gboolean matcher_is_blank(gchar ch)
68 {
69         return (ch == ' ') || (ch == '\t');
70 }
71
72 /* parse for one condition */
73
74 MatcherProp * matcherprop_parse(gchar ** str)
75 {
76         MatcherProp * prop;
77         gchar * tmp;
78         gint key;
79         gint age;
80         gchar * expr;
81         gint match;
82         gchar * header = NULL;
83         
84         tmp = * str;
85         key = matcher_parse_keyword(&tmp);
86         if (tmp == NULL) {
87                 * str = NULL;
88                 return NULL;
89         }
90
91         switch (key) {
92         case MATCHING_AGE_LOWER:
93         case MATCHING_AGE_GREATER:
94                 age = matcher_parse_number(&tmp);
95                 if (tmp == NULL) {
96                         * str = NULL;
97                         return NULL;
98                 }
99                 *str = tmp;
100
101                 prop = matcherprop_new(key, NULL, 0, NULL, age);
102
103                 return prop;
104
105         case MATCHING_ALL:
106                 prop = matcherprop_new(key, NULL, 0, NULL, 0);
107                 *str = tmp;
108
109                 return prop;
110
111         case MATCHING_SUBJECT:
112         case MATCHING_NOT_SUBJECT:
113         case MATCHING_FROM:
114         case MATCHING_NOT_FROM:
115         case MATCHING_TO:
116         case MATCHING_NOT_TO:
117         case MATCHING_CC:
118         case MATCHING_NOT_CC:
119         case MATCHING_TO_OR_CC:
120         case MATCHING_NOT_TO_AND_NOT_CC:
121         case MATCHING_NEWSGROUPS:
122         case MATCHING_NOT_NEWSGROUPS:
123         case MATCHING_MESSAGE:
124         case MATCHING_NOT_MESSAGE:
125         case MATCHING_HEADERS_PART:
126         case MATCHING_NOT_HEADERS_PART:
127         case MATCHING_BODY_PART:
128         case MATCHING_NOT_BODY_PART:
129         case MATCHING_HEADER:
130         case MATCHING_NOT_HEADER:
131                 if ((key == MATCHING_HEADER) || (key == MATCHING_NOT_HEADER)) {
132                         header = matcher_parse_str(&tmp);
133                         if (tmp == NULL) {
134                                 * str = NULL;
135                                 return NULL;
136                         }
137                 }
138
139                 match = matcher_parse_keyword(&tmp);
140                 if (tmp == NULL) {
141                         if (header)
142                                 g_free(header);
143                         * str = NULL;
144                         return NULL;
145                 }
146
147                 switch(match) {
148                 case MATCHING_REGEXP:
149                 case MATCHING_REGEXPCASE:
150                         expr = matcher_parse_regexp(&tmp);
151                         if (tmp == NULL) {
152                                 if (header)
153                                         g_free(header);
154                                 * str = NULL;
155                                 return NULL;
156                         }
157                         *str = tmp;
158                         prop = matcherprop_new(key, header, match, expr, 0);
159                         g_free(expr);
160
161                         return prop;
162                 case MATCHING_MATCH:
163                 case MATCHING_MATCHCASE:
164                         expr = matcher_parse_str(&tmp);
165                         if (tmp == NULL) {
166                                 if (header)
167                                         g_free(header);
168                                 * str = NULL;
169                                 return NULL;
170                         }
171                         *str = tmp;
172                         prop = matcherprop_new(key, header, match, expr, 0);
173                         g_free(expr);
174
175                         return prop;
176                 default:
177                         if (header)
178                                 g_free(header);
179                         * str = NULL;
180                         return NULL;
181                 }
182         default:
183                 * str = NULL;
184                 return NULL;
185         }
186 }
187
188 gint matcher_parse_keyword(gchar ** str)
189 {
190         gchar * p;
191         gchar * dup;
192         gchar * start;
193         gint i;
194         gint match;
195
196         dup = alloca(strlen(* str) + 1);
197         p = dup;
198         strcpy(dup, * str);
199
200         while (matcher_is_blank(*p))
201                 p++;
202
203         start = p;
204         
205         match = -1;
206         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
207             i++) {
208                 if (strncasecmp(matchparser_tab[i].str, p,
209                                 strlen(matchparser_tab[i].str)) == 0) {
210                         p += strlen(matchparser_tab[i].str);
211                         match = i;
212                         break;
213                 }
214         }
215
216         if (match == -1) {
217                 * str = NULL;
218                 return 0;
219         }
220
221         *p = '\0';
222
223         *str += p - dup + 1;
224         return matchparser_tab[match].id;
225 }
226
227 gint matcher_parse_number(gchar ** str)
228 {
229         gchar * p;
230         gchar * dup;
231         gchar * start;
232
233         dup = alloca(strlen(* str) + 1);
234         p = dup;
235         strcpy(dup, * str);
236
237         while (matcher_is_blank(*p))
238                 p++;
239
240         start = p;
241
242         if (!isdigit(*p) && *p != '-' && *p != '+') {
243                 *str = NULL;
244                 return 0;
245         }
246         if (*p == '-' || *p == '+')
247                 p++;
248         while (isdigit(*p))
249                 p++;
250
251         *p = '\0';
252
253         *str += p - dup + 1;
254         return atoi(start);
255 }
256
257 gboolean matcher_parse_boolean_op(gchar ** str)
258 {
259         gchar * p;
260
261         p = * str;
262
263         while (matcher_is_blank(*p))
264                 p++;
265
266         if (*p == '|') {
267                 *str += p - * str + 1;
268                 return FALSE;
269         }
270         else if (*p == '&') {
271                 *str += p - * str + 1;
272                 return TRUE;
273         }
274         else {
275                 *str = NULL;
276                 return FALSE;
277         }
278 }
279
280 gchar * matcher_parse_regexp(gchar ** str)
281 {
282         gchar * p;
283         gchar * dup;
284         gchar * start;
285
286         dup = alloca(strlen(* str) + 1);
287         p = dup;
288         strcpy(dup, * str);
289
290         while (matcher_is_blank(*p))
291                 p++;
292
293         if (*p != '/') {
294                 * str = NULL;
295                 return NULL;
296         }
297
298         p ++;
299         start = p;
300         while (*p != '/') {
301                 if (*p == '\\')
302                         p++;
303                 p++;
304         }
305         *p = '\0';
306
307         *str += p - dup + 2;
308         return g_strdup(start);
309 }
310
311 gchar * matcher_parse_str(gchar ** str)
312 {
313         gchar * p;
314         gchar * dup;
315         gchar * start;
316         gchar * dest;
317
318         dup = alloca(strlen(* str) + 1);
319         p = dup;
320         strcpy(dup, * str);
321
322         while (matcher_is_blank(*p))
323                 p++;
324
325         if (*p != '"') {
326                 * str = NULL;
327                 return NULL;
328         }
329         
330         p ++;
331         start = p;
332         dest = p;
333         while (*p != '"') {
334                 if (*p == '\\') {
335                         p++;
336                         *dest = *p;
337                 }
338                 else
339                         *dest = *p;
340                 dest++;
341                 p++;
342         }
343         *dest = '\0';
344
345         *str += dest - dup + 2;
346         return g_strdup(start);
347 }
348
349 /* **************** data structure allocation **************** */
350
351
352 MatcherProp * matcherprop_new(gint criteria, gchar * header,
353                               gint matchtype, gchar * expr,
354                               int age)
355 {
356         MatcherProp * prop;
357
358         prop = g_new0(MatcherProp, 1);
359         prop->criteria = criteria;
360         if (header != NULL)
361                 prop->header = g_strdup(header);
362         else
363                 prop->header = NULL;
364         if (expr != NULL)
365                 prop->expr = g_strdup(expr);
366         else
367                 prop->expr = NULL;
368         prop->matchtype = matchtype;
369         prop->preg = NULL;
370         prop->age = age;
371         prop->error = 0;
372
373         return prop;
374 }
375
376 void matcherprop_free(MatcherProp * prop)
377 {
378         g_free(prop->expr);
379         if (prop->preg != NULL) {
380                 regfree(prop->preg);
381                 g_free(prop->preg);
382         }
383         g_free(prop);
384 }
385
386
387 /* ************** match ******************************/
388
389
390 /* match the given string */
391
392 static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str)
393 {
394         gchar * str1;
395
396         if (str == NULL)
397                 str = "";
398
399         switch(prop->matchtype) {
400         case MATCHING_REGEXP:
401         case MATCHING_REGEXPCASE:
402                 if (!prop->preg && (prop->error == 0)) {
403                         prop->preg = g_new0(regex_t, 1);
404                         if (regcomp(prop->preg, prop->expr,
405                                     REG_NOSUB | REG_EXTENDED
406                                     | ((prop->matchtype == MATCHING_REGEXPCASE)
407                                     ? REG_ICASE : 0)) != 0) {
408                                 prop->error = 1;
409                                 g_free(prop->preg);
410                         }
411                 }
412                 if (prop->preg == NULL)
413                         return 0;
414                 
415                 if (regexec(prop->preg, str, 0, NULL, 0) == 0)
416                         return 1;
417                 else
418                         return 0;
419
420         case MATCHING_MATCH:
421                 return (strstr(str, prop->expr) != NULL);
422
423         case MATCHING_MATCHCASE:
424                 g_strup(prop->expr);
425                 str1 = alloca(strlen(str) + 1);
426                 strcpy(str1, str);
427                 g_strup(str1);
428                 return (strstr(str1, prop->expr) != NULL);
429                 
430         default:
431                 return 0;
432         }
433 }
434
435 /* match a message and his headers, hlist can be NULL if you don't
436    want to use headers */
437
438 gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info)
439 {
440         time_t t;
441
442         switch(prop->criteria) {
443         case MATCHING_ALL:
444                 return 1;
445         case MATCHING_SUBJECT:
446                 return matcherprop_string_match(prop, info->subject);
447         case MATCHING_NOT_SUBJECT:
448                 return !matcherprop_string_match(prop, info->subject);
449         case MATCHING_FROM:
450                 return matcherprop_string_match(prop, info->from);
451         case MATCHING_NOT_FROM:
452                 return !matcherprop_string_match(prop, info->from);
453         case MATCHING_TO:
454                 return matcherprop_string_match(prop, info->to);
455         case MATCHING_NOT_TO:
456                 return !matcherprop_string_match(prop, info->to);
457         case MATCHING_CC:
458                 return matcherprop_string_match(prop, info->cc);
459         case MATCHING_NOT_CC:
460                 return !matcherprop_string_match(prop, info->cc);
461         case MATCHING_TO_OR_CC:
462                 return matcherprop_string_match(prop, info->to)
463                         || matcherprop_string_match(prop, info->cc);
464         case MATCHING_NOT_TO_AND_NOT_CC:
465                 return !(matcherprop_string_match(prop, info->to)
466                 || matcherprop_string_match(prop, info->cc));
467         case MATCHING_AGE_GREATER:
468                 t = time(NULL);
469                 return ((t - info->date_t) / (60 * 60 * 24)) >= prop->age;
470         case MATCHING_AGE_LOWER:
471                 t = time(NULL);
472                 return ((t - info->date_t) / (60 * 60 * 24)) <= prop->age;
473         case MATCHING_NEWSGROUPS:
474                 return matcherprop_string_match(prop, info->newsgroups);
475         case MATCHING_NOT_NEWSGROUPS:
476                 return !matcherprop_string_match(prop, info->newsgroups);
477         case MATCHING_HEADER:
478         default:
479                 return 0;
480         }
481 }
482
483 /* ********************* MatcherList *************************** */
484
485
486 /* parse for a list of conditions */
487
488 MatcherList * matcherlist_parse(gchar ** str)
489 {
490         gchar * tmp;
491         MatcherProp * matcher;
492         GSList * matchers_list = NULL;
493         gboolean bool_and = TRUE;
494         gchar * save;
495         MatcherList * cond;
496         gboolean main_bool_and = TRUE;
497
498         tmp = * str;
499
500         matcher = matcherprop_parse(&tmp);
501         if (tmp == NULL) {
502                 * str = NULL;
503                 return NULL;
504         }
505         matchers_list = g_slist_append(matchers_list, matcher);
506         while (matcher) {
507                 save = tmp;
508                 bool_and = matcher_parse_boolean_op(&tmp);
509                 if (tmp == NULL) {
510                         tmp = save;
511                         matcher = NULL;
512                 }
513                 else {
514                         main_bool_and = bool_and;
515                         matcher = matcherprop_parse(&tmp);
516                         if (tmp != NULL) {
517                                 matchers_list =
518                                         g_slist_append(matchers_list, matcher);
519                         }
520                         else {
521                                 * str = NULL;
522                                 return NULL;
523                         }
524                 }
525         }
526
527         cond = matcherlist_new(matchers_list, main_bool_and);
528
529         * str = tmp;
530
531         return cond;
532 }
533
534 MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and)
535 {
536         MatcherList * cond;
537
538         cond = g_new0(MatcherList, 1);
539
540         cond->matchers = matchers;
541         cond->bool_and = bool_and;
542
543         return cond;
544 }
545
546 void matcherlist_free(MatcherList * cond)
547 {
548         GSList * l;
549         for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
550                 matcherprop_free((MatcherProp *) l->data);
551         }
552         g_free(cond);
553 }
554
555 /*
556   skip the headers
557  */
558
559 static void matcherlist_skip_headers(FILE *fp)
560 {
561         gchar buf[BUFFSIZE];
562
563         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
564         }
565 }
566
567 /*
568   matcherprop_match_one_header
569   returns TRUE if buf matchs the MatchersProp criteria
570  */
571
572 static gboolean matcherprop_match_one_header(MatcherProp * matcher,
573                                              gchar * buf)
574 {
575         gboolean result;
576         Header *header;
577
578         switch(matcher->criteria) {
579         case MATCHING_HEADER:
580         case MATCHING_NOT_HEADER:
581                 header = procheader_parse_header(buf);
582                 if (procheader_headername_equal(header->name,
583                                                 matcher->header)) {
584                         if (matcher->criteria == MATCHING_HEADER)
585                                 result = matcherprop_string_match(matcher, header->body);
586                         else
587                                 result = !matcherprop_string_match(matcher, header->body);
588                         procheader_header_free(header);
589                         return result;
590                 }
591                 break;
592         case MATCHING_HEADERS_PART:
593         case MATCHING_MESSAGE:
594                 return matcherprop_string_match(matcher, buf);
595         case MATCHING_NOT_MESSAGE:
596         case MATCHING_NOT_HEADERS_PART:
597                 return !matcherprop_string_match(matcher, buf);
598         }
599         return FALSE;
600 }
601
602 /*
603   matcherprop_criteria_header
604   returns TRUE if the headers must be matched
605  */
606
607 static gboolean matcherprop_criteria_headers(MatcherProp * matcher)
608 {
609         switch(matcher->criteria) {
610         case MATCHING_HEADER:
611         case MATCHING_NOT_HEADER:
612         case MATCHING_HEADERS_PART:
613         case MATCHING_NOT_HEADERS_PART:
614         case MATCHING_MESSAGE:
615         case MATCHING_NOT_MESSAGE:
616                 return TRUE;
617         default:
618                 return FALSE;
619         }
620 }
621
622 /*
623   matcherlist_match_one_header
624   returns TRUE if buf matchs the MatchersList criteria
625  */
626
627 static gboolean matcherlist_match_one_header(MatcherList * matchers,
628                                              gchar * buf, gboolean result)
629 {
630         GSList * l;
631         
632         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
633                 MatcherProp * matcher = (MatcherProp *) l->data;
634
635                 if (matcherprop_criteria_headers(matcher)) {
636                         if (matcherprop_match_one_header(matcher, buf)) {
637                                 if (!matchers->bool_and)
638                                         return TRUE;
639                         }
640                         else {
641                                 if (matchers->bool_and)
642                                         return FALSE;
643                         }
644                 }
645         }
646
647         return result;
648 }
649
650 /*
651   matcherlist_match_headers
652   returns TRUE if one of the headers matchs the MatcherList criteria
653  */
654
655 static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp,
656                                           gboolean result)
657 {
658         gchar buf[BUFFSIZE];
659
660         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
661                 if (matcherlist_match_one_header(matchers, buf, result)) {
662                         if (!matchers->bool_and)
663                                 return TRUE;
664                 }
665                 else {
666                         if (matchers->bool_and)
667                                 return FALSE;
668                 }
669         }
670         return result;
671 }
672
673 /*
674   matcherprop_criteria_body
675   returns TRUE if the body must be matched
676  */
677
678 static gboolean matcherprop_criteria_body(MatcherProp * matcher)
679 {
680         switch(matcher->criteria) {
681         case MATCHING_BODY_PART:
682         case MATCHING_NOT_BODY_PART:
683         case MATCHING_MESSAGE:
684         case MATCHING_NOT_MESSAGE:
685                 return TRUE;
686         default:
687                 return FALSE;
688         }
689 }
690
691 /*
692   matcherprop_match_line
693   returns TRUE if the string matchs the MatcherProp criteria
694  */
695
696 static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line)
697 {
698         switch(matcher->criteria) {
699         case MATCHING_BODY_PART:
700         case MATCHING_MESSAGE:
701                 return matcherprop_string_match(matcher, line);
702         case MATCHING_NOT_BODY_PART:
703         case MATCHING_NOT_MESSAGE:
704                 return !matcherprop_string_match(matcher, line);
705         }
706         return FALSE;
707 }
708
709 /*
710   matcherlist_match_line
711   returns TRUE if the string matchs the MatcherList criteria
712  */
713
714 static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line,
715                                        gboolean result)
716 {
717         GSList * l;
718
719         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
720                 MatcherProp * matcher = (MatcherProp *) l->data;
721
722                 if (matcherprop_criteria_body(matcher)) {
723                         if (matcherprop_match_line(matcher, line)) {
724                                 if (!matchers->bool_and)
725                                         return TRUE;
726                         }
727                         else {
728                                 if (matchers->bool_and)
729                                         return FALSE;
730                         }
731                 }
732         }
733         return result;
734 }
735
736 /*
737   matcherlist_match_body
738   returns TRUE if one line of the body matchs the MatcherList criteria
739  */
740
741 static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp,
742                                        gboolean result)
743 {
744         gchar buf[BUFFSIZE];
745
746         while (fgets(buf, sizeof(buf), fp) != NULL) {
747                 if (matcherlist_match_line(matchers, buf, result)) {
748                         if (!matchers->bool_and)
749                                 return TRUE;
750                 }
751                 else {
752                         if (matchers->bool_and)
753                                 return FALSE;
754                 }
755         }
756         return result;
757 }
758
759 gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info,
760                                 gboolean result)
761 {
762         gboolean read_headers;
763         gboolean read_body;
764         GSList * l;
765         FILE * fp;
766         gchar * file;
767
768         /* file need to be read ? */
769
770         read_headers = FALSE;
771         read_body = FALSE;
772         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
773                 MatcherProp * matcher = (MatcherProp *) l->data;
774
775                 if (matcherprop_criteria_headers(matcher))
776                         read_headers = TRUE;
777                 if (matcherprop_criteria_body(matcher))
778                         read_body = TRUE;
779         }
780
781         if (!read_headers && !read_body)
782                 return result;
783
784         file = procmsg_get_message_file(info);
785         if (file == NULL)
786                 return FALSE;
787
788         if ((fp = fopen(file, "r")) == NULL) {
789                 FILE_OP_ERROR(file, "fopen");
790                 g_free(file);
791                 return result;
792         }
793         
794         /* read the headers */
795
796         if (read_headers) {
797                 if (matcherlist_match_headers(matchers, fp, result)) {
798                         if (!matchers->bool_and)
799                                 result = TRUE;
800                 }
801                 else {
802                         if (matchers->bool_and)
803                                 result = FALSE;
804                 }
805         }
806         else {
807                 matcherlist_skip_headers(fp);
808         }
809         
810         /* read the body */
811         if (read_body) {
812                 if (matcherlist_match_body(matchers, fp, result)) {
813                         if (!matchers->bool_and)
814                                 result = TRUE;
815                 }
816                 else {
817                         if (matchers->bool_and)
818                                 result = FALSE;
819                 }
820         }
821
822         g_free(file);
823
824         fclose(fp);
825         
826         return result;
827 }
828
829 /* test a list of condition */
830
831 gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info)
832 {
833         GSList * l;
834         gboolean result;
835
836         if (matchers->bool_and)
837                 result = TRUE;
838         else
839                 result = FALSE;
840
841         /* test the condition on the file */
842
843         if (matcherlist_match_file(matchers, info, result)) {
844                 if (!matchers->bool_and)
845                         return TRUE;
846         }
847         else {
848                 if (matchers->bool_and)
849                         return FALSE;
850         }
851
852         /* test the cached elements */
853
854         for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
855                 MatcherProp * matcher = (MatcherProp *) l->data;
856
857                 if (matcherprop_match(matcher, info)) {
858                         if (!matchers->bool_and) {
859                                 result = TRUE;
860                                 break;
861                         }
862                 }
863                 else {
864                         if (matchers->bool_and) {
865                                 result = FALSE;
866                                 break;
867                         }
868                 }
869         }
870
871         return result;
872 }
873
874 #ifdef 0
875 static void matcherprop_print(MatcherProp * matcher)
876 {
877   int i;
878
879         if (matcher == NULL) {
880                 printf("no matcher\n");
881                 return;
882         }
883
884         switch (matcher->matchtype) {
885         case MATCHING_MATCH:
886                 printf("match\n");
887                 break;
888         case MATCHING_REGEXP:
889                 printf("regexp\n");
890                 break;
891         case MATCHING_MATCHCASE:
892                 printf("matchcase\n");
893                 break;
894         case MATCHING_REGEXPCASE:
895                 printf("regexpcase\n");
896                 break;
897         }
898
899         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
900             i++) {
901                 if (matchparser_tab[i].id == matcher->criteria)
902                         printf("%s\n", matchparser_tab[i].str);
903         }
904
905         if (matcher->expr)
906                 printf("expr : %s\n", matcher->expr);
907
908         printf("age: %i\n", matcher->age);
909
910         printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no");
911         printf("error: %i\n",  matcher->error);
912 }
913 #endif
914
915 gchar * matcherprop_to_string(MatcherProp * matcher)
916 {
917         gchar * matcher_str;
918         gchar * criteria_str;
919         gchar * matchtype_str;
920         int i;
921         gchar * p;
922         gint count;
923         gchar * expr_str;
924         gchar * out;
925
926         criteria_str = NULL;
927         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
928             i++) {
929                 if (matchparser_tab[i].id == matcher->criteria)
930                         criteria_str = matchparser_tab[i].str;
931         }
932         if (criteria_str == NULL)
933                 return NULL;
934
935         switch(matcher->criteria) {
936         case MATCHING_AGE_GREATER:
937         case MATCHING_AGE_LOWER:
938                 return g_strdup_printf("%s %i", criteria_str, matcher->age);
939                 break;
940         case MATCHING_ALL:
941                 return g_strdup(criteria_str);
942         }
943
944         matchtype_str = NULL;
945         for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ;
946             i++) {
947                 if (matchparser_tab[i].id == matcher->matchtype)
948                         matchtype_str = matchparser_tab[i].str;
949         }
950
951         if (matchtype_str == NULL)
952                 return NULL;
953
954         switch (matcher->matchtype) {
955         case MATCHING_MATCH:
956         case MATCHING_MATCHCASE:
957                 count = 0;
958                 for(p = matcher->expr; *p != 0 ; p++)
959                         if (*p == '\"') count ++;
960                 
961                 expr_str = g_new(char, strlen(matcher->expr) + count + 1);
962
963                 for(p = matcher->expr, out = expr_str ; *p != 0 ; p++, out++) {
964                         if (*p == '\"') {
965                                 *out = '\\'; out++;
966                                 *out = '\"';
967                         }
968                         else
969                                 *out = *p;
970                 }
971                 * out = '\0';
972
973                 if (matcher->header)
974                         matcher_str =
975                                 g_strdup_printf("%s \"%s\" %s \"%s\"",
976                                            criteria_str, matcher->header,
977                                            matchtype_str, expr_str);
978                 else
979                         matcher_str =
980                                 g_strdup_printf("%s %s \"%s\"", criteria_str,
981                                                 matchtype_str, expr_str);
982                 
983                 g_free(expr_str);
984                 
985                 break;
986
987         case MATCHING_REGEXP:
988         case MATCHING_REGEXPCASE:
989
990                 if (matcher->header)
991                         matcher_str =
992                                 g_strdup_printf("%s \"%s\" %s /%s/",
993                                                 criteria_str, matcher->header,
994                                                 matchtype_str, matcher->expr);
995                 else
996                         matcher_str =
997                                 g_strdup_printf("%s %s /%s/", criteria_str,
998                                                 matchtype_str, matcher->expr);
999
1000                 break;
1001         }
1002
1003         return matcher_str;
1004 }
1005
1006 gchar * matcherlist_to_string(MatcherList * matchers)
1007 {
1008         gint count;
1009         gchar ** vstr;
1010         GSList * l;
1011         gchar ** cur_str;
1012         gchar * result;
1013
1014         count = g_slist_length(matchers->matchers);
1015         vstr = g_new(gchar *, count + 1);
1016
1017         for (l = matchers->matchers, cur_str = vstr ; l != NULL ;
1018              l = g_slist_next(l), cur_str ++) {
1019                 *cur_str = matcherprop_to_string((MatcherProp *) l->data);
1020                 if (*cur_str == NULL)
1021                         break;
1022         }
1023         *cur_str = NULL;
1024         
1025         if (matchers->bool_and)
1026                 result = g_strjoinv(" & ", vstr);
1027         else
1028                 result = g_strjoinv(" | ", vstr);
1029
1030         for(cur_str = vstr ; *cur_str != NULL ; cur_str ++)
1031                 g_free(*cur_str);
1032         g_free(vstr);
1033
1034         return result;
1035 }