%{ /* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2003-2007, 2009-2012, 2014-2016 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */ #include <mh.h> #include <regex.h> #include <pick.h> static node_t *pick_node_create (node_type type, void *a, void *b); static void set_cflags (char const *str); static regex_t * regex_dup (regex_t *re) { regex_t *rp = mu_alloc (sizeof (*rp)); *rp = *re; return rp; } int yyerror (const char *s); int yylex (void); static node_t *parse_tree; static int nesting_level; static int reg_flags = REG_EXTENDED|REG_ICASE; %} %token <string> T_COMP T_DATEFIELD T_STRING T_CFLAGS %token T_LBRACE T_RBRACE T_BEFORE T_AFTER %left T_OR %left T_AND %left T_NOT %union { char *string; node_t *node; regex_t regex; }; %type <node> expr exprlist %type <regex> regex %% input : /* empty */ { parse_tree = NULL; } | exprlist { parse_tree = $1; } ; exprlist : expr | exprlist expr { $$ = pick_node_create (node_and, $1, $2); } ; cflags : /* empty */ | T_CFLAGS { set_cflags ($1); } ; regex : cflags T_STRING { int rc = regcomp (&$$, $2, reg_flags|REG_NOSUB); if (rc) { char errbuf[512]; regerror (rc, &$$, errbuf, sizeof (errbuf)); mu_error ("error compiling regex \"%s\": %s", $2, errbuf); YYERROR; } } ; expr : lbrace exprlist rbrace { $$ = $2; } | cflags T_COMP regex { $$ = pick_node_create (node_regex, $2, regex_dup (&$3)); } | regex { $$ = pick_node_create (node_regex, NULL, regex_dup (&$1)); } | T_DATEFIELD { $$ = pick_node_create (node_datefield, $1, NULL); } | T_BEFORE T_STRING { time_t t; if (mu_parse_date ($2, &t, NULL)) { mu_error (_("bad date format: %s"), $2); exit (1); } $$ = pick_node_create (node_before, NULL, NULL); $$->v.time = t; } | T_AFTER T_STRING { time_t t; if (mu_parse_date ($2, &t, NULL)) { mu_error (_("bad date format: %s"), $2); exit (1); } $$ = pick_node_create (node_after, NULL, NULL); $$->v.time = t; } | expr T_AND expr { $$ = pick_node_create (node_and, $1, $3); } | expr T_OR expr { $$ = pick_node_create (node_or, $1, $3); } | T_NOT expr { $$ = pick_node_create (node_not, $2, NULL); } ; lbrace : T_LBRACE { nesting_level++; } ; rbrace : T_RBRACE { nesting_level--; } ; %% /* Lexical analizer */ struct token { int tok; char *val; }; static mu_iterator_t iterator; int yylex () { struct token *tok; if (mu_iterator_is_done (iterator)) return 0; mu_iterator_current (iterator, (void **)&tok); mu_iterator_next (iterator); yylval.string = tok->val; return tok->tok; } static char * tokname (int tok) { switch (tok) { case T_DATEFIELD: return "-datefield"; case T_BEFORE: return "-before"; case T_AFTER: return "-after"; case T_LBRACE: return "-lbrace"; case T_RBRACE: return "-rbrace"; case T_OR: return "-or"; case T_AND: return "-and"; case T_NOT: return "-not"; } return NULL; } int yyerror (const char *s) { int tok = yylex (); const char *str; if (!tok) str = _("end of input"); else if (yylval.string) str = yylval.string; else str = tokname (tok); if (nesting_level) mu_error (_("%s near %s (missing closing brace?)"), s, str); else mu_error (_("%s near %s"), s, str); return 0; } void pick_add_token (mu_list_t *list, int tok, char const *val) { struct token *tp; int rc; if (!*list && (rc = mu_list_create (list))) { mu_error(_("cannot create list: %s"), mu_strerror (rc)); exit (1); } tp = mu_alloc (sizeof (*tp)); tp->tok = tok; tp->val = (char*) val; mu_list_append (*list, tp); } /* Main entry point */ int pick_parse (mu_list_t toklist) { int rc; if (!toklist) { parse_tree = NULL; return 0; } if (mu_list_get_iterator (toklist, &iterator)) return -1; mu_iterator_first (iterator); rc = yyparse (); mu_iterator_destroy (&iterator); return rc; } /* Parse tree functions */ node_t * pick_node_create (node_type type, void *a, void *b) { node_t *node; node = mu_alloc (sizeof (*node)); node->type = type; node->v.gen.a = a; node->v.gen.b = b; return node; } struct eval_env { mu_message_t msg; char *datefield; }; static int match_header (mu_message_t msg, char *comp, regex_t *regex) { int rc; size_t i, count; mu_header_t hdr = NULL; const char *buf; rc = mu_message_get_header (msg, &hdr); if (rc) { mu_error (_("cannot get header: %s"), mu_strerror (rc)); return 0; } mu_header_get_field_count (hdr, &count); for (i = 1; i <= count; i++) { mu_header_sget_field_name (hdr, i, &buf); if (mu_c_strcasecmp (buf, comp) == 0) { mu_header_sget_field_value (hdr, i, &buf); if (regexec (regex, buf, 0, NULL, 0) == 0) return 1; } } return 0; } static int match_message (mu_message_t msg, regex_t *regex) { mu_stream_t str = NULL; char buf[128]; size_t n; mu_message_get_streamref (msg, &str); while (mu_stream_readline (str, buf, sizeof buf, &n) == 0 && n > 0) { buf[n] = 0; if (regexec (regex, buf, 0, NULL, 0) == 0) return 1; } mu_stream_destroy (&str); return 0; } static int get_date_field (struct eval_env *env, time_t *t) { mu_header_t hdr; char buf[128]; if (mu_message_get_header (env->msg, &hdr)) return 1; if (mu_header_get_value (hdr, env->datefield, buf, sizeof buf, NULL)) return 1; return mu_parse_date (buf, t, NULL); } static int pick_eval_node (node_t *node, struct eval_env *env) { time_t t; switch (node->type) { case node_and: if (!pick_eval_node (node->v.op.larg, env)) return 0; return pick_eval_node (node->v.op.rarg, env); case node_or: if (pick_eval_node (node->v.op.larg, env)) return 1; return pick_eval_node (node->v.op.rarg, env); case node_not: return !pick_eval_node (node->v.op.larg, env); case node_regex: if (node->v.re.comp) return match_header (env->msg, node->v.re.comp, node->v.re.regex); else return match_message (env->msg, node->v.re.regex); case node_datefield: env->datefield = node->v.df.datefield; return 1; case node_before: if (get_date_field (env, &t)) break; return t < node->v.time; case node_after: if (get_date_field (env, &t)) break; return t > node->v.time; } return 0; } int pick_eval (mu_message_t msg) { struct eval_env env; if (!parse_tree) return 1; env.msg = msg; env.datefield = "date"; return pick_eval_node (parse_tree, &env); } void set_cflags (char const *str) { reg_flags = 0; for (; *str; str++) { switch (*str) { case 'b': case 'B': reg_flags &= ~REG_EXTENDED; break; case 'e': case 'E': reg_flags |= REG_EXTENDED; break; case 'c': case 'C': reg_flags &= ~REG_ICASE; break; case 'i': case 'I': reg_flags |= REG_ICASE; break; default: mu_error (_("Invalid regular expression flag: %c"), *str); exit (1); } } }