source: filter.c @ 01dcae5

owl
Last change on this file since 01dcae5 was 01dcae5, checked in by James M. Kretchmar <kretch@mit.edu>, 15 years ago
sprintf fixes for owl_filter_print, owl_function_printallvars, owl_function_show_filter owl_function_show_zpunts, owl_function_zpunt adopt ^(un)*%s(\\.d) style regex for punting
  • Property mode set to 100644
File size: 16.9 KB
RevLine 
[dab82f29]1/* Copyright (c) 2002,2003,2004,2009 James M. Kretchmar
2 *
3 * This file is part of Owl.
4 *
5 * Owl is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * Owl 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 Owl.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 * ---------------------------------------------------------------
19 *
20 * As of Owl version 2.1.12 there are patches contributed by
[fa00c5c]21 * developers of the branched BarnOwl project, Copyright (c)
22 * 2006-2009 The BarnOwl Developers. All rights reserved.
[dab82f29]23 */
24
[7d4fbcd]25#include <string.h>
26#include "owl.h"
27
[1aee7d9]28static const char fileIdent[] = "$Id$";
29
[40458b9]30#define OWL_FILTER_MAXRECURSE 20
31
[e187445]32int owl_filter_init_fromstring(owl_filter *f, char *name, char *string)
33{
[7d4fbcd]34  char **argv;
35  int argc, out;
36
37  argv=owl_parseline(string, &argc);
38  out=owl_filter_init(f, name, argc, argv);
[dab82f29]39  owl_parsefree(argv, argc);
[7d4fbcd]40  return(out);
41}
42
[e187445]43int owl_filter_init(owl_filter *f, char *name, int argc, char **argv)
44{
[40458b9]45  int i, j, error;
[7d4fbcd]46  owl_filterelement *fe;
[65fc0900]47  char *regexstr;
[40458b9]48  owl_list list;
[7d4fbcd]49   
50  f->name=owl_strdup(name);
51  f->polarity=0;
52  f->color=OWL_COLOR_DEFAULT;
[59cf91c]53  f->cachedmsgid=-1;
[7d4fbcd]54  owl_list_create(&(f->fes));
[65fc0900]55 
[7d4fbcd]56  /* first take arguments that have to come first */
57  /* set the color */
58  if (argc>=2 && !strcmp(argv[0], "-c")) {
[12c35df]59    if (owl_util_string_to_color(argv[1])==-1) {
60      owl_function_error("The color '%s' is not available, using default.", argv[1]);
61    } else {
62      f->color=owl_util_string_to_color(argv[1]);
63    }
[7d4fbcd]64    argc-=2;
65    argv+=2;
66  }
[65fc0900]67 
[7d4fbcd]68  /* then deal with the expression */
69  for (i=0; i<argc; i++) {
70    error=0;
71    fe=owl_malloc(sizeof(owl_filterelement));
[65fc0900]72   
[7d4fbcd]73    /* all the 0 argument possibilities */
74    if (!strcmp(argv[i], "(")) {
75      owl_filterelement_create_openbrace(fe);
76    } else if (!strcmp(argv[i], ")")) {
77      owl_filterelement_create_closebrace(fe);
78    } else if (!strcasecmp(argv[i], "and")) {
79      owl_filterelement_create_and(fe);
80    } else if (!strcasecmp(argv[i], "or")) {
81      owl_filterelement_create_or(fe);
82    } else if (!strcasecmp(argv[i], "not")) {
83      owl_filterelement_create_not(fe);
[89426ab]84    } else if (!strcasecmp(argv[i], "true")) {
85      owl_filterelement_create_true(fe);
86    } else if (!strcasecmp(argv[i], "false")) {
87      owl_filterelement_create_false(fe);
[65fc0900]88     
[40458b9]89    } else if (i==argc-1) { /* we need more than one arg at this point */
[7d4fbcd]90      error=1;
91    } else {
92      if (!strcasecmp(argv[i], "class") ||
93          !strcasecmp(argv[i], "instance") ||
94          !strcasecmp(argv[i], "sender") ||
95          !strcasecmp(argv[i], "recipient") ||
[75be7c0]96          !strcasecmp(argv[i], "body") ||
[7d4fbcd]97          !strcasecmp(argv[i], "opcode") ||
98          !strcasecmp(argv[i], "realm") ||
[4b464a4]99          !strcasecmp(argv[i], "type") ||
[0c502e9]100          !strcasecmp(argv[i], "direction") ||
[49d467c]101          !strcasecmp(argv[i], "hostname") ||
[0c502e9]102          !strcasecmp(argv[i], "login")) {
[e3d9c77]103        regexstr=owl_text_substitute(argv[i+1], "%me%", owl_zephyr_get_sender());
[65fc0900]104        owl_filterelement_create_re(fe, argv[i], regexstr);
105        owl_free(regexstr);
[7d4fbcd]106        i++;
[40458b9]107      } else if (!strcasecmp(argv[i], "filter")) {
108        owl_filterelement_create_filter(fe, argv[i+1]);
109        i++;
[32eed98]110      } else if (!strcasecmp(argv[i], "perl")) {
111        owl_filterelement_create_perl(fe, argv[i+1]);
112        i++;
[7d4fbcd]113      } else {
114        error=1;
115      }
116    }
117
118    if (!error) {
119      owl_list_append_element(&(f->fes), fe);
120    } else {
121      owl_free(fe);
122      owl_filter_free(f);
123      return(-1);
124    }
[40458b9]125  }
126 
127  /* Are we trying to use the filter we're creating?  Bad. */
128  owl_list_create(&list);
129  _owl_filter_get_subfilter_names(f, &list);
130  j=owl_list_get_size(&list);
131  for (i=0; i<j; i++) {
132    if (!strcasecmp(name, owl_list_get_element(&list, i))) {
133      owl_function_error("Filter loop.");
134      owl_filter_free(f);
135      owl_list_free_all(&list, owl_free);
136      return(-1);
137    }
138  }
139  owl_list_free_all(&list, owl_free);
[7d4fbcd]140
[40458b9]141  /* Now check for more subtle recursion. */
142  if (owl_filter_is_toodeep(f)) {
143    owl_function_error("Filter loop or exceeds recursion depth");
144    owl_filter_free(f);
145    return(-1);
[7d4fbcd]146  }
[40458b9]147 
[7d4fbcd]148  return(0);
149}
150
[e187445]151char *owl_filter_get_name(owl_filter *f)
152{
[7d4fbcd]153  return(f->name);
154}
155
[e187445]156void owl_filter_set_polarity_match(owl_filter *f)
157{
[7d4fbcd]158  f->polarity=0;
159}
160
[e187445]161void owl_filter_set_polarity_unmatch(owl_filter *f)
162{
[7d4fbcd]163  f->polarity=1;
164}
165
[e187445]166void owl_filter_set_color(owl_filter *f, int color)
167{
[7d4fbcd]168  f->color=color;
169}
170
[e187445]171int owl_filter_get_color(owl_filter *f)
172{
[7d4fbcd]173  return(f->color);
174}
175
[e187445]176void owl_filter_set_cachedmsgid(owl_filter *f, int cachedmsgid)
177{
[59cf91c]178  f->cachedmsgid=cachedmsgid;
179}
180
[e187445]181int owl_filter_get_cachedmsgid(owl_filter *f)
182{
[59cf91c]183  return(f->cachedmsgid);
184}
185
[15b34fd]186/* return 1 if the message matches the given filter, otherwise
187 * return 0.
188 */
[e187445]189int owl_filter_message_match(owl_filter *f, owl_message *m)
190{
[7d4fbcd]191  int i, j, tmp;
192  owl_list work_fes, *fes;
193  owl_filterelement *fe;
194  char *field, *match;
195
[d09e5a1]196  /* create the working list of expression elements */
[7d4fbcd]197  fes=&(f->fes);
198  owl_list_create(&work_fes);
199  j=owl_list_get_size(fes);
200  for (i=0; i<j; i++) {
201    owl_list_append_element(&work_fes, owl_list_get_element(fes, i));
202  }
203
[d09e5a1]204  /* first go thru and evaluate all RE elements to true or false */
[7d4fbcd]205  match="";
206  for (i=0; i<j; i++) {
207    fe=owl_list_get_element(&work_fes, i);
208    if (!owl_filterelement_is_re(fe)) continue;
209    field=owl_filterelement_get_field(fe);
210    if (!strcasecmp(field, "class")) {
211      match=owl_message_get_class(m);
212    } else if (!strcasecmp(field, "instance")) {
213      match=owl_message_get_instance(m);
214    } else if (!strcasecmp(field, "sender")) {
215      match=owl_message_get_sender(m);
216    } else if (!strcasecmp(field, "recipient")) {
217      match=owl_message_get_recipient(m);
[75be7c0]218    } else if (!strcasecmp(field, "body")) {
219      match=owl_message_get_body(m);
[7d4fbcd]220    } else if (!strcasecmp(field, "opcode")) {
221      match=owl_message_get_opcode(m);
222    } else if (!strcasecmp(field, "realm")) {
223      match=owl_message_get_realm(m);
224    } else if (!strcasecmp(field, "type")) {
[37eab7f]225      match=owl_message_get_type(m);
[49d467c]226    } else if (!strcasecmp(field, "hostname")) {
227      match=owl_message_get_hostname(m);
[4b464a4]228    } else if (!strcasecmp(field, "direction")) {
229      if (owl_message_is_direction_out(m)) {
230        match="out";
231      } else if (owl_message_is_direction_in(m)) {
232        match="in";
233      } else if (owl_message_is_direction_none(m)) {
234        match="none";
235      } else {
236        match="";
237      }
[0c502e9]238    } else if (!strcasecmp(field, "login")) {
239      if (owl_message_is_login(m)) {
240        match="login";
241      } else if (owl_message_is_logout(m)) {
242        match="logout";
243      } else {
244        match="none";
245      }
[7d4fbcd]246    }
[40458b9]247
[7d4fbcd]248    tmp=owl_regex_compare(owl_filterelement_get_re(fe), match);
249    if (!tmp) {
250      owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_true(&g));
251    } else {
252      owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_false(&g));
253    }
254  }
255
[32eed98]256  /* now subfilters and perl functions */
[40458b9]257  for (i=0; i<j; i++) {
258    owl_filter *subfilter;
259                           
260    fe=owl_list_get_element(&work_fes, i);
261
[32eed98]262    if (owl_filterelement_is_filter(fe)) {
263
264      subfilter=owl_global_get_filter(&g, owl_filterelement_get_filtername(fe));
265      if (!subfilter) {
266        /* the filter does not exist, maybe because it was deleted.
267         * Default to not matching
268         */
269        owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_false(&g));
270      } else if (owl_filter_message_match(subfilter, m)) {
271        owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_true(&g));
272      } else {
273        owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_false(&g));
274      }
275
276    } else if (owl_filterelement_is_perl(fe)) {
277      char *subname, *perlrv;
278      int   tf=0;
279
280      subname = owl_filterelement_get_filtername(fe);
281      if (!owl_perlconfig_is_function(subname)) {
282        owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_false(&g));
283        continue;
284      }
285      perlrv = owl_perlconfig_call_with_message(subname, m);
286      if (perlrv) {
287        if (0 == strcmp(perlrv, "1")) {
288          tf=1;
289        }
290        owl_free(perlrv);
291      } 
292      if (tf) {
293        owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_true(&g));
294      } else {
295        owl_list_replace_element(&work_fes, i, owl_global_get_filterelement_false(&g));
296      }
[40458b9]297    }
298  }
299
[75be7c0]300  /* call the recrsive helper */
[7d4fbcd]301  i=_owl_filter_message_match_recurse(f, m, &work_fes, 0, owl_list_get_size(&(f->fes))-1);
302
[d09e5a1]303  /* now there will be only one TRUE / FALSE, find it among the NULL's */
[7d4fbcd]304  tmp=0;
305  for (i=0; i<j; i++) {
306    fe=owl_list_get_element(&work_fes, i);
307    if (owl_filterelement_is_null(fe)) continue;
308    if (owl_filterelement_is_true(fe)) {
309      tmp=1;
310      break;
311    }
312    if (owl_filterelement_is_false(fe)) {
313      tmp=0;
314      break;
315    }
316  } 
317
[d09e5a1]318  /* reverse the answer if negative polarity is in use */
319  if (f->polarity) tmp=!tmp;
320
[7d4fbcd]321  owl_list_free_simple(&work_fes);
322  return(tmp);
323}
324
[e187445]325int _owl_filter_message_match_recurse(owl_filter *f, owl_message *m, owl_list *fes, int start, int end)
326{
[7d4fbcd]327  int a=0, b=0, i, x, y, z, score, ret, type;
328  owl_filterelement *fe, *tmpfe=NULL;
329
[d09e5a1]330  /* Deal with parens first. */
[7d4fbcd]331  for (i=0; i<OWL_FILTER_MAX_DEPTH; i++) {
[d09e5a1]332    /* Find first open paren and matching close paren, store in x, y */
[7d4fbcd]333    score=x=y=0;
334    for (i=start; i<=end; i++) {
335      fe=owl_list_get_element(fes, i);
336      if (owl_filterelement_is_openbrace(fe)) {
337        if (score==0) x=i;
338        score++;
339      } else if (owl_filterelement_is_closebrace(fe)) {
340        score--;
341        if (score<0) {
342          /* unblanaced parens */
343          return(-1);
344        } else if (score==0) {
345          y=i; /* this is the matching close paren */
346          break;
347        }
348      }
349    }
350    if (score>0) {
351      /* unblanaced parens */
352      return(-1);
353    }
354
[d09e5a1]355    /* Simply the parens by removing them and evaluating what was in between */
[7d4fbcd]356    if (y>0) {
357      /* null out the parens */
358      owl_list_replace_element(fes, x, owl_global_get_filterelement_null(&g));
359      owl_list_replace_element(fes, y, owl_global_get_filterelement_null(&g));
360
[d09e5a1]361      /* evaluate expression in between */
[7d4fbcd]362      ret=_owl_filter_message_match_recurse(f, m, fes, x+1, y-1);
363      if (ret<0) return(-1);
364
365      /* there may be more, so we continue */
366      continue;
367    } else {
368      /* otherwise we're done with this part */
369      break;
370    }
371  }
372  if (i==OWL_FILTER_MAX_DEPTH) {
[d09e5a1]373    /* hit the saftey limit, consider it invalid */
[7d4fbcd]374    return(-1);
375  }
376
[d09e5a1]377  /* Find AND / OR / NOT.
378   *   For binary expressions (AND/OR):
379   *     "type" is 1
380   *     "x" will index first val, "y" the operator and "z" the second val
381   *   For unary expressions (NOT):
382   *     "type" is 2
383   *     "x" will index the operator, "y" the value
384   *   "score" tallys how many expression elements have been found so far
385   */
[7d4fbcd]386  for (i=0; i<OWL_FILTER_MAX_DEPTH; i++) {
387    type=score=x=y=z=0;
388    for (i=start; i<=end; i++) {
389      fe=owl_list_get_element(fes, i);
390      if (owl_filterelement_is_null(fe)) continue;
391      if (score==0) {
392        if (owl_filterelement_is_value(fe)) {
393          x=i;
394          score=1;
395          type=1;
396        } else if (owl_filterelement_is_not(fe)) {
397          x=i;
398          score=1;
399          type=2;
400        }
401      } else if (score==1) {
402        if (type==1) {
403          if (owl_filterelement_is_and(fe) || owl_filterelement_is_or(fe)) {
404            score=2;
405            y=i;
406          } else {
[d09e5a1]407            /* it's not a valid binary expression */
[7d4fbcd]408            x=y=z=score=0;
409          }
410        } else if (type==2) {
411          if (owl_filterelement_is_value(fe)) {
[d09e5a1]412            /* valid unary expression, we're done */
[7d4fbcd]413            y=i;
414            break;
415          }
416        }
417      } else if (score==2) {
418        if (owl_filterelement_is_value(fe)) {
[d09e5a1]419          /* valid binary expression, we're done */
[7d4fbcd]420          z=i;
421          break;
422        } else {
423          x=y=z=score=0;
424        }
425      }
426    }
427
[d09e5a1]428    /* simplify AND / OR */
[7d4fbcd]429    if ((type==1) && (z>0)) {
430      fe=owl_list_get_element(fes, x);
431      if (owl_filterelement_is_true(fe)) {
432        a=1;
433      } else if (owl_filterelement_is_false(fe)) {
434        a=0;
435      }
436
437      fe=owl_list_get_element(fes, z);
438      if (owl_filterelement_is_true(fe)) {
439        b=1;
440      } else if (owl_filterelement_is_false(fe)) {
441        b=0;
442      }
443
444      fe=owl_list_get_element(fes, y);
445      if (owl_filterelement_is_and(fe)) {
446        if (a && b) {
447          tmpfe=owl_global_get_filterelement_true(&g);
448        } else {
449          tmpfe=owl_global_get_filterelement_false(&g);
450        }
451      } else if (owl_filterelement_is_or(fe)) {
452        if (a || b) {
453          tmpfe=owl_global_get_filterelement_true(&g);
454        } else {
455          tmpfe=owl_global_get_filterelement_false(&g);
456        }
457      }
458      owl_list_replace_element(fes, x, owl_global_get_filterelement_null(&g));
459      owl_list_replace_element(fes, y, tmpfe);
460      owl_list_replace_element(fes, z, owl_global_get_filterelement_null(&g));
[d09e5a1]461    } else if ((type==2) && (y>0)) {
462      /* simplify NOT */
[7d4fbcd]463      fe=owl_list_get_element(fes, y);
464      owl_list_replace_element(fes, x, owl_global_get_filterelement_null(&g));
465      if (owl_filterelement_is_false(fe)) {
466        owl_list_replace_element(fes, y, owl_global_get_filterelement_true(&g));
467      } else {
468        owl_list_replace_element(fes, y, owl_global_get_filterelement_false(&g));
469      }
470    } else {
471      break;
472    }
473  }
474  return(0);
475
476}
477
[01dcae5]478char *owl_filter_print(owl_filter *f)
[e187445]479{
[7d4fbcd]480  int i, j;
481  owl_filterelement *fe;
[01dcae5]482  GString *out = g_string_new("");
[7d4fbcd]483
484  if (f->color!=OWL_COLOR_DEFAULT) {
[01dcae5]485    g_string_append(out, "-c ");
486    if (f->color < 8) {
487      g_string_append(out, owl_util_color_to_string(f->color));
488    } else {
489      g_string_append_printf(out, "%i",f->color);
490    }
491    g_string_append(out, " ");
[7d4fbcd]492  }
493
494  j=owl_list_get_size(&(f->fes));
495  for (i=0; i<j; i++) {
496    fe=owl_list_get_element(&(f->fes), i);
[01dcae5]497    g_string_append(out, owl_filterelement_to_string(fe));
[7d4fbcd]498  }
[01dcae5]499
500  return g_string_free(out, 0);
[7d4fbcd]501}
502
[40458b9]503/* Return 1 if the filters 'a' and 'b' are equivalent, 0 otherwise */
[e187445]504int owl_filter_equiv(owl_filter *a, owl_filter *b)
505{
[01dcae5]506  char *buffa, *buffb;
507  int ret;
[7d4fbcd]508
[01dcae5]509  buffa = owl_filter_print(a);
510  buffb = owl_filter_print(b);
[7d4fbcd]511
[01dcae5]512  ret = !strcmp(buffa, buffb);
513  ret = ret && !strcmp(owl_filter_get_name(a),
514                       owl_filter_get_name(b));
515
516  owl_free(buffa);
517  owl_free(buffb);
518
519  return ret;
[7d4fbcd]520}
521
[40458b9]522/* Private
523 * 'list' should already be allocated and initialized
524 * This function places into list the string names of all filters
525 * used in the filter expression for 'f'.
526 * Caller must do a full free on 'list', including elements.
527 */
528void _owl_filter_get_subfilter_names(owl_filter *f, owl_list *list)
529{
530  int i, j;
531  owl_filterelement *fe;
532
533  j=owl_list_get_size(&(f->fes));
534  for (i=0; i<j; i++) {
535    fe=owl_list_get_element(&(f->fes), i);
536    if (owl_filterelement_is_filter(fe)) {
537      owl_list_append_element(list, owl_strdup(owl_filterelement_get_filtername(fe)));
538    }
539  }
540}
541
542int owl_filter_is_toodeep(owl_filter *f)
543{
544  owl_list seen, tocheck, tmp;
545  int i, j, x, y;
546  owl_filter *subfilter;
547
548  owl_list_create(&seen);
549  owl_list_create(&tocheck);
550  owl_list_create(&tmp);
551
552  /* seed 'tocheck' with the first set of filters */
553  _owl_filter_get_subfilter_names(f, &tmp);
554  j=owl_list_get_size(&tmp);
555  for (i=0; i<j; i++) {
556    owl_util_list_add_unique_string(&tocheck, owl_list_get_element(&tmp, i));
557  }
558  owl_list_free_all(&tmp, owl_free);
559  owl_list_create(&tmp);
560
561  /* add this list to the 'seen' list */
562  owl_list_append_element(&seen, owl_strdup(owl_filter_get_name(f)));
563
564  for (i=0; i<OWL_FILTER_MAXRECURSE; i++) {
565    /* if anything in 'tocheck' is in 'seen' we have a loop */
566    if (owl_util_common_strings_in_lists(&tocheck, &seen)) {
567      owl_list_free_all(&tmp, owl_free);
568      owl_list_free_all(&tocheck, owl_free);
569      owl_list_free_all(&seen, owl_free);
570      return(1);
571    }
572
573    /* if there's nothing to check, we're done */
574    y=owl_list_get_size(&tocheck);
575    if (y==0) {
576      owl_list_free_all(&tmp, owl_free);
577      owl_list_free_all(&tocheck, owl_free);
578      owl_list_free_all(&seen, owl_free);
579      return(0);
580    }
581
582    /* add everything in 'tocheck' to the 'seen' list */
583    /* y=owl_list_get_size(&tocheck); */
584    for (x=0; x<y; x++) {
585      owl_list_append_element(&seen, owl_strdup(owl_list_get_element(&tocheck, x)));
586    }
587
588    /* make a new list into 'tmp' with the children of 'tocheck' */
589    /* y=owl_list_get_size(&tocheck); */
590    for (x=0; x<y; x++) {
591      subfilter=owl_global_get_filter(&g, owl_list_get_element(&tocheck, x));
592      if (!subfilter) return(0);
593      _owl_filter_get_subfilter_names(subfilter, &tmp);
594    }
595
596    /* clean out 'tocheck' */
597    owl_list_free_all(&tocheck, owl_free);
598    owl_list_create(&tocheck);
599
600    /* put 'tmp' into 'tocheck' */
601    y=owl_list_get_size(&tmp);
602    for (x=0; x<y; x++) {
603      owl_util_list_add_unique_string(&tocheck, owl_list_get_element(&tmp, x));
604    }
605
606    /* clean out 'tmp' */
607    owl_list_free_all(&tmp, owl_free);
608    owl_list_create(&tmp);
609  }
610
611  owl_list_free_all(&tmp, owl_free);
612  owl_list_free_all(&tocheck, owl_free);
613  owl_list_free_all(&seen, owl_free);
614
615  return(1);
616}
617
[e187445]618void owl_filter_free(owl_filter *f)
619{
[7d4fbcd]620  void (*func)();
621
622  func=&owl_filterelement_free;
623 
624  if (f->name) owl_free(f->name);
625  owl_list_free_all(&(f->fes), func);
626}
Note: See TracBrowser for help on using the repository browser.