source: filter.c @ b699e9b

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