Manitou-Mail logo title

Source file: mdx/lib/Manitou/Filters.pm

# Copyright (C) 2004-2012 Daniel Verite

# This file is part of Manitou-Mail (see http://www.manitou-mail.org)

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.

# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

# Filtering code for manitou-mdx

use strict;

package Manitou::Filters;

use Data::Dumper;
use MIME::Head;
use MIME::Parser;
use MIME::Words qw(:all);
use Manitou::Encoding;
use Manitou::Config qw(getconf_bool);
use DBI;

my $filter_exprs;

# ctxt->evp: position in expression during left->right traversal
# ctxt->expr: current expression
# ctxt->len: equal to length(ctxt->expr)
# ctxt->npar: current level of parenthesis
# ctxt->errstr: error string
# ctxt->evstack: list of operand values handled as a stack
# ctxt->mime_obj: mime object for current mail
# ctxt->mail_id: mail_id of current mail if available
# ctxt->call_stack: stack of condition names being evaluated (used to prevent infinite recursion)

my $PRI_DOT=10;
my $PRI_AND=24;
my $PRI_OR=22;
my $PRI_UNARY_NOT=30;
my $PRI_CMP=40;
my $PRI_CONTAINS=40;
my $PRI_REGEXP=40;

my $TYPE_STRING=1;
my $TYPE_FUNC=2;
my $TYPE_NUMBER=3;
my $TYPE_SUBEXPR=4;
my $TYPE_PSTRING=5;
my $TYPE_MAILID=6;

my %eval_binary_ops =
  (
   "and" => {"func" => sub { return $_[0] && $_[1]; },
	     "pri" => $PRI_AND},
   "or"  => {"func" => sub { return $_[0] || $_[1]; },
	     "pri" => $PRI_OR},
   "contains"  => {"func" => sub { return index($_[0],$_[1])>=0; },
	     "pri" => $PRI_CONTAINS},
   # alias for 'contains'
   "contain"  => {"func" => sub { $_[0] =~ /\Q$_[1]\E/i; },
	     "pri" => $PRI_CONTAINS},
   "equals"  => {"func" => sub { $_[0] eq $_[1]; },
	     "pri" => $PRI_CMP},
   "eq"  => {"func" => sub { $_[0] eq $_[1]; },
	     "pri" => $PRI_CMP},
   "ne"  => {"func" => sub { $_[0] ne $_[1]; },
	     "pri" => $PRI_CMP},
   "is"  => {"func" => sub { uc($_[0]) eq uc($_[1]); },
	     "pri" => $PRI_CMP},
   "isnot"  => {"func" => sub { uc($_[0]) ne uc($_[1]); },
	     "pri" => $PRI_CMP},
   "regmatches"  => {"func" => sub { $_[0] =~ /$_[1]/ },
	     "pri" => $PRI_REGEXP}
  );

my %eval_unary_ops =
  (
   "not" => {"func" => sub { return !$_[0]; },
	     "pri" => $PRI_UNARY_NOT}
  );


sub get_parent {
  my ($mailobj,$dbh) = @_;
  my $in_reply_to=$mailobj->head->get('In-Reply-To');
  my $sth=$dbh->prepare("SELECT mail_id FROM mail WHERE message_id=?");
  if ($in_reply_to =~ /.*\<(.*)\>/) {
    $in_reply_to=$1;
    $sth->execute($in_reply_to);
    my @r=$sth->fetchrow_array;
    return $r[0] if (@r);
  }

  my $other_mails=$mailobj->head->get('References');
  my @thread_msgs=split / /, $other_mails;
  for my $m (reverse(@thread_msgs)) {
    $m=$1 if ($m =~ /.*\<(.*)\>/);
    $sth->execute($m);
    my @r=$sth->fetchrow_array;
    return $r[0] if (@r);
  }
  undef;
}

# sub func_parent {
#   my ($ctxt)=@_;
#   my $mail_id=$ctxt->{mail_id};
#   if ($mail_id) {
#     my $sth=$ctxt->{dbh}->prepare("SELECT in_reply_to FROM mail WHERE mail_id=?");
#     $sth->execute($mail_id);
#     my @r=$sth->fetchrow_array;
#     return $r[0];		# may be undef
#   }
#   elsif ($ctxt->{mime_obj}) {
#     return get_parent($ctxt->{mime_obj}, $ctxt->{dbh});
#   }
#   undef;
# }

sub list_addresses {
  my ($o, $field)=@_;
  my @a;
  eval {
    @a = Mail::Address->parse(Manitou::Encoding::header_decode($o->head->get($field)));
  };
  return join ',', map { $_->address() } @a;
}

sub func_mimeobj_size {
  my $top=shift;
  my $size=0;
  if ($top->is_multipart) {
    foreach my $p ($top->parts) {
      $size += func_mimeobj_size($p);
    }
  }
  else {
    if (defined $top->bodyhandle) {
      $size = length($top->body);
    }
  }
  return $size;
}

sub date_field {
  my $ctxt=shift;
  my $field=shift;
  my %fmts=("hour"=>"%H", "minute"=>"%M", "second"=>"%S",
	    "day"=>"%d", "month"=>"%m", "year"=>"%Y", "weekday"=>"%w",
	    "date"=>"%Y-%m-%d", time=>"%H:%M:%S");
  my $f = $fmts{lc($field)};
  if (!defined $f) {
    $ctxt->{errstr} = "Invalid date/time field '$field'";
    return undef;
  }
  POSIX::strftime($f, @_);
}

my %eval_funcs =
  (
   "age" =>
   {"func" => sub {
      my ($ctxt,$u)=@_;
      my %units=("days"=>86400, "hours"=>3600, "minutes"=>60);
      if (!exists $units{$u}) {
	$ctxt->{errstr} = "age() argument must be \"days\", \"hours\", or \"minutes\"";
	return undef;
      }
      my $h=$ctxt->{mime_obj}->head->get("Date");
      return undef if (!defined $h);
      my $t = Manitou::MailFormat::convert_sender_date_to_timestamp($h);
      return undef if (!defined $t);
      return (time()-$t)/$units{$u};
    },
    "args" => 1,
    "return_type" => $TYPE_NUMBER
   },

   "body" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      return $o->bodyhandle->as_string if ($o->bodyhandle);
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "cc" =>
   {"func" => sub {
      return list_addresses($_[0]->{mime_obj}, "cc");
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "condition" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my ($v,$t) = eval_subexpr($ctxt, $_[1]);
      return ($v, $t);
    },
    "args" => 1,
    "return_type" => $TYPE_NUMBER
   },


   "date" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $h=$ctxt->{mime_obj}->head->get("Date");
      return undef if (!defined $h);
      my $t = Manitou::MailFormat::convert_sender_date_to_timestamp($h);
      return undef if (!defined $t);
      date_field($ctxt, $_[1], localtime($t));
    },
    "args" => 1,
    "return_type" => $TYPE_STRING
   },

   "date_utc" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $h=$ctxt->{mime_obj}->head->get("Date");
      return undef if (!defined $h);
      my $t = Manitou::MailFormat::convert_sender_date_to_timestamp($h);
      return undef if (!defined $t);
      date_field($ctxt, $_[1], gmtime($t));
    },
    "args" => 1,
    "return_type" => $TYPE_STRING
   },

   "from" =>
   {"func" => sub {
      return list_addresses($_[0]->{mime_obj}, "from");
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "header" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      my $v = $o->head->get($_[1]);
      chomp $v;
      return Manitou::Encoding::header_decode($v);
    },
    "args" => 1,
    "return_type" => $TYPE_STRING
   },

   "headers" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      if (!exists $ctxt->{cache}->{headers}) {
	$ctxt->{cache}->{headers}=Manitou::Encoding::header_decode($o->head->as_string);
      }
      return $ctxt->{cache}->{headers};
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "identity" =>
   {"func" => sub {
      return $_[0]->{mailbox_address};
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "rawheader" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      my $v = $o->head->get($_[1]);
      chomp $v;
      return $v;
    },
    "args" => 1,
    "return_type" => $TYPE_STRING
   },

   "rawheaders" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      return $o->head->as_string;
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "rawsize" =>
   {"func" => sub {
      my $ctxt=$_[0];
      return $ctxt->{mail_ctxt}->{filesize};
    },
    "args"=>0,
    "return_type"=>$TYPE_NUMBER,
   },

   "recipients" =>   # Returns all recipients (To, Cc, Bcc) separated by commas
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      if (exists $ctxt->{cache}->{recipients}) {
	return $ctxt->{cache}->{recipients};
      }
      my $r;
      for my $h ("To", "Cc", "Bcc") {
	my $v = Manitou::Encoding::header_decode($o->head->get($h));
	$r = defined $r ? "$r,$v" : $v;
      }
      return $ctxt->{cache}->{recipients}=$r;
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "now" =>
   {"func" => sub { date_field($_[0], $_[1], localtime($_[0]->{start_time})); },
    "args" => 1,
    "return_type" => $TYPE_STRING
   },

   "now_utc" =>
   {"func" => sub { date_field($_[0], $_[1], gmtime($_[0]->{start_time})); },
    "args" => 1,
    "return_type" => $TYPE_STRING
   },

   "subject" =>
   {"func" => sub {
      my $ctxt=$_[0];
      my $o=$ctxt->{mime_obj};
      my $v = $o->head->get("subject");
      chomp $v;
      Manitou::Encoding::header_decode($v);
    },
    "args" => 0,
    "return_type" => $TYPE_STRING
   },

   "to" =>
   {"func" => sub { list_addresses($_[0]->{mime_obj}, "to"); },
    "args" => 0,
    "return_type" => $TYPE_STRING
   }

  );

sub nxchar {
  my ($ctxt)=@_;
  # test against the length to avoid the warning emitted by substr()
  # when trying to get a character outside of the string
  if ($ctxt->{evp} < $ctxt->{len}) {
    return substr($ctxt->{expr}, $ctxt->{evp}, 1);
  }
  return undef;
}

sub nxchar1 {
  my ($ctxt)=@_;
  if ($ctxt->{evp}+1 < $ctxt->{len}) {
    return substr($ctxt->{expr}, $ctxt->{evp}+1, 1);
  }
  return undef;
}

sub skip_blanks {
  my ($ctxt)=@_;
  my $c;
  my $p=$ctxt->{evp};
  do {
    $c=substr($ctxt->{expr}, $p, 1);
    $p++;
  } while ($c eq " " || $c eq "\t");
  $ctxt->{evp}=$p-1;
}

sub getnum {
  my ($ctxt)=@_;
  my $v=0;
  my $end=0;
  my $c;
  my $p=$ctxt->{evp};
  do {
    $c=substr($ctxt->{expr}, $p++, 1);
    if ($c =~ /[0-9]/) {
      $v = $v*10+$c;
    }
    else {
	# k,m or g following a number means kbytes,mbytes or gbytes
      my $m=lc($c);
      if ($m eq "k") { $v *= 1024; }
      elsif ($m eq "m") { $v *= 1024*1024; }
      elsif ($m eq "g") { $v *= 1024*1024*1024 }
      else { $p--; }
      $end=1;
    }
  } while (!$end);
  $ctxt->{evp}=$p;
  push @{$ctxt->{evstack}}, [$v, $TYPE_NUMBER];
}

sub eval_subexpr {
  my ($ctxt,$exprname)=@_;
  if (defined($ctxt->{cache_subexpr}->{$exprname})) {
    return @{$ctxt->{cache_subexpr}->{$exprname}};
  }
  elsif (defined($filter_exprs->{$exprname})) {
    # Prevent infinite recursion
    if (grep { $_ eq $exprname } @{$ctxt->{call_stack}}) {
      $ctxt->{errstr}="Infinite recursion requested";
      return undef;
    }

    my $evstr=$filter_exprs->{$exprname}->{expr};
    my %subctxt;
    $subctxt{mime_obj}=$ctxt->{mime_obj};
    $subctxt{evp}=0;
    $subctxt{cache} = $ctxt->{cache};
    $subctxt{expr}=$evstr;
    $subctxt{len}=length($evstr);
    $subctxt{call_stack} = $ctxt->{call_stack};
    push @{$subctxt{call_stack}}, $exprname;
    if (eval_expr(0, \%subctxt)) {
      my ($pv2) = pop(@{$subctxt{evstack}});
      $ctxt->{cache_subexpr}->{$exprname}=@{$pv2};
      return @{$pv2};
    }
    else {
      $ctxt->{errstr} = "Error $exprname: ". $subctxt{errstr}. " at character ". $subctxt{evp}+1;
      return undef;
    }
  }
  else {
    $ctxt->{errstr}="Unknown function \"$exprname\"";
    return undef;
  }
}

# Returns the symbol in lower case
sub getsym {
  my ($ctxt)=@_;
  my $v;
  my $end=0;
  my $c;
  my $p=$ctxt->{evp};
  do {
    $c=lc(substr($ctxt->{expr}, $p, 1));
    $p++;
    if ($c =~ /[a-z_0-9]/) { $v .= $c; }
    else { $end=1; }
  } while (!$end);
  $ctxt->{evp}=$p-1;
  return $v;
}

sub get_op_name {
  my ($ctxt)=@_;
  my $v;
  my $end=0;
  my $c;
  my $p=$ctxt->{evp};
  do {
    $c=substr($ctxt->{expr}, $p++, 1);
    if ($c =~ /[A-Za-z_0-9]/) { $v .= lc($c); }
    else { $end=1; }
  } while (!$end);
  $ctxt->{evp}=$p-1;
  return $v;
}

# parse a string that should be ended by the $endc character
# interpret backslash-style quoting if ($quote)
sub eval_string {
  my ($ctxt, $endc, $quote)=@_;
  my $end=0;
  my $c;
  my $v;
  my $p=$ctxt->{evp};
  my $l=$ctxt->{len};
  while (!$end) {
    if ($p==$l) {
      $ctxt->{errstr}="Premature end of string";
      $ctxt->{evp}=$p-1;
      return undef;
    }
    else {
      $c=substr($ctxt->{expr}, $p++, 1);
      if ($quote && $c eq "\\") {
	if ($p<$l) {
	  $c = substr($ctxt->{expr}, $p++, 1);
	  $v .= $c;
	}
	else {
	  next; # and error
	}
      }
      elsif ($c eq $endc) {
	$end=1;
      }
      else {
	$v .= $c;
      }
    }
  }
  $ctxt->{evp}=$p;
  push @{$ctxt->{evstack}}, [$v,$TYPE_STRING];
  1;
}

sub process_binary_op {
  my ($ctxt, $c, $pri)=@_;
  $ctxt->{evp}++;
  if (eval_expr($pri, $ctxt)) {
    my $pv2=pop(@{$ctxt->{evstack}});
    my $pv1=pop(@{$ctxt->{evstack}});
    my ($v2,$t2)=@{$pv2};
    my ($v1,$t1)=@{$pv1};
    my $res;
    if ($c eq "=") {
      $res = (uc($v1) eq uc($v2))?1:0;
    }
    elsif ($c eq "==") {
      $res = ($v1 == $v2)?1:0;
    }
    elsif ($c eq "=~") {
      $res = ($v1 =~ /$v2/i)?1:0;
    }
    elsif ($c eq "!~") {
      $res = ($v1 !~ /$v2/i)?1:0;
    }
    elsif ($c eq "<") {
      $res = ($v1 < $v2)?1:0;
    }
    elsif ($c eq ">") {
      $res = ($v1 > $v2)?1:0;
    }
    elsif ($c eq ">=") {
      $res = ($v1 >= $v2)?1:0;
    }
    elsif ($c eq "<=") {
      $res = ($v1 <= $v2)?1:0;
    }
    elsif ($c eq "!=") {
      $res = ($v1 != $v2)?1:0;
    }
    push(@{$ctxt->{evstack}}, [$res, $TYPE_NUMBER]);
  }
}

sub eval_expr {
  my ($level, $ctxt)=@_;
  my $endexpr=0;

  skip_blanks($ctxt);

  #### operand or unary operator followed by operand ####
  my $c=nxchar($ctxt);
  if ($c eq "(") {
    $ctxt->{npar}++;
    $ctxt->{evp}++;
    if (eval_expr(0, $ctxt) && nxchar($ctxt) ne ")") {
      $ctxt->{errstr}="Unmatched parenthesis";
    }
    $ctxt->{npar}--;
    $ctxt->{evp}++;		# eat ')'
  }
  elsif ($c eq ")") {
    # We're accepting parentheses around an empty content, but only
    # in the context of evaluating function arguments
    my $last_elt=@{$ctxt->{evstack}};
    if ($last_elt>0 && $ctxt->{npar}>0) {
      my $pv=@{$ctxt->{evstack}}[$last_elt-1];
      if (@{$pv}[1] == $TYPE_FUNC) {
	$endexpr=1;
      }
    }
    if (!$endexpr) {
      $ctxt->{errstr}="Unmatched closing parenthesis";
    }
  }
  elsif ($c eq '"' || $c eq "'") {
    $ctxt->{evp}++;
    return undef if (!eval_string($ctxt, $c, 0));
  }
  elsif ($c eq '\\') {
    my $c1=nxchar1($ctxt);
    if ($c1 eq '"' || $c1 eq "'") {
      $ctxt->{evp}+=2;
      return undef if (!eval_string($ctxt, $c1, 1));
    }
    else {
      $ctxt->{errstr}="Unexpected character '\\'";
    }
  }
  elsif ($c eq "!") {		# logical not
    if ($level <= $PRI_UNARY_NOT) {
      $ctxt->{evp}++;
      if (eval_expr($PRI_UNARY_NOT, $ctxt)) {
	my $pv=pop(@{$ctxt->{evstack}});
	my ($v,$t)=@{$pv};
	push(@{$ctxt->{evstack}}, [!$v,$t]);
      }
    }
    else { $endexpr=1; }
  }
  elsif ($c =~ /^[0-9]$/) {
    getnum($ctxt);
  }
  elsif (lc($c) =~ /^[a-z_]$/) {
    my $startp=$ctxt->{evp};
    my $sym=getsym($ctxt);
    if (defined $eval_unary_ops{$sym}) {
      my $f=$eval_unary_ops{$sym};
      if ($level <= $f->{pri}) {
	if (eval_expr($f->{pri}, $ctxt)) {
	  my $pv=pop(@{$ctxt->{evstack}});
	  my ($v,$t)=@{$pv};
	  my $fn=$f->{func};
	  push(@{$ctxt->{evstack}}, [&$fn($v),$t]);
	}
      }
      else {
	$ctxt->{evp}=$startp;
	$endexpr=1;
      }
    }
    else {
      my $pfunc = $eval_funcs{$sym};
      if (defined $pfunc) {
	skip_blanks($ctxt);
	if (nxchar($ctxt) eq '(') {
	  $ctxt->{evp}++;
	  $ctxt->{npar}++;
	  skip_blanks($ctxt);
	  if (nxchar($ctxt) eq ')') {
	    $ctxt->{evp}++;
	    $ctxt->{npar}--;
	    if ($pfunc->{args}==0) {
	      my $fn = $pfunc->{func};
	      my $v = &$fn($ctxt);
	      push(@{$ctxt->{evstack}}, [$v,$pfunc->{return_type}]);
	    }
	    else {
	      $ctxt->{errstr} = "Missing argument to function '$sym'";
	    }
	  }
	  else {
	    if ($pfunc->{args}==0) {
	      $ctxt->{errstr} = "The function '$sym' does not accept any argument";
	    }
	    else {
	      my $stack_depth = @{$ctxt->{evstack}};
	      if (eval_expr(0, $ctxt)) {
		if (nxchar($ctxt) ne ')') {
		  $ctxt->{errstr} = "Unmatched parenthesis";
		}
		else {
		  $ctxt->{npar}--;
		  $ctxt->{evp}++;
		  my ($v_arg,$t);
		  if ($stack_depth != @{$ctxt->{evstack}}) {
		    my $pv = pop @{$ctxt->{evstack}};
		    ($v_arg,$t) = @{$pv};
		  }
		  my $fn=$pfunc->{func};
		  my $v=&$fn($ctxt,$v_arg,$t);
		  push @{$ctxt->{evstack}}, [$v, $pfunc->{return_type}];
		}
	      }
	      else {
		$endexpr=1;
	      }
	    }
	  }
	}
	else {
	  # no open parenthesis
	  if ($pfunc->{args}==0) {
	    my $fn=$pfunc->{func};
	    push @{$ctxt->{evstack}}, [&$fn($ctxt), $pfunc->{return_type}];
	  }
	  else {
	    $ctxt->{errstr}="Open parenthesis expected after function requiring arguments";
	  }
	}
      }
      else {
	# sub-expr, evaluate now
	my ($v,$t)=eval_subexpr($ctxt, $sym);
	if (!defined($v)) {
	  $ctxt->{evp}=$startp;		# step back to the error
	  return undef;
	}
	push @{$ctxt->{evstack}}, [$v,$t] ;
      }
    }
  }
  else {
    # default
    $ctxt->{errstr} = "Parse error";
  }
  return undef if ($ctxt->{errstr});

  #### binary operator and 2nd operand ####
  while (!$endexpr && !defined($ctxt->{errstr})) {
    if ($ctxt->{evp} >= length($ctxt->{expr})) {
      $endexpr=1;
      last;
    }
    skip_blanks($ctxt);
    $c=nxchar($ctxt);
    if ($c eq ")") {
      if ($ctxt->{npar}==0) {
	$ctxt->{errstr}="Unmatched closing parenthesis";
      }
      $endexpr=1;
    }
    elsif ($c =~ /[A-Za-z]/) {
      my $p=$ctxt->{evp};
      my $o=get_op_name($ctxt);
      my $f=$eval_binary_ops{$o};
      if (defined($f)) {
#	print Dumper($f);
	if ($level <= $f->{pri}) {
	  if (eval_expr($f->{pri}, $ctxt)) {
	    my $pv2=pop(@{$ctxt->{evstack}});
	    my $pv1=pop(@{$ctxt->{evstack}});
	    my ($v2,$t2)=@{$pv2};
	    my ($v1,$t1)=@{$pv1};
	    my $fn=$f->{func};
	    my $v=&$fn($v1,$v2);
	    push(@{$ctxt->{evstack}}, [$v, $TYPE_NUMBER]);
	  }
	}
	else {
	  $ctxt->{evp}=$p;
	  $endexpr=1;
	}
      }
      else {
	$ctxt->{evp}=$p;
	$ctxt->{errstr} = "Unknown operator";
      }
    }
    elsif ($c eq "=") {
      my $c1=nxchar1($ctxt);
      if ($c1 eq "~") {
	if ($level <= $PRI_REGEXP) {
	  $ctxt->{evp}++;
	  process_binary_op($ctxt, "=~", $PRI_REGEXP);
	}
	else { $endexpr=1; }
      }
      else {
	if ($level <= $PRI_CMP) {
	  process_binary_op($ctxt, "=", $PRI_CMP);
	}
	else { $endexpr=1; }
      }
    }
    elsif ($c eq "!") {
      my $c1=nxchar1($ctxt);
      if ($c1 eq "~") {
	if ($level <= $PRI_REGEXP) {
	  $ctxt->{evp}++;
	  process_binary_op($ctxt, "!~", $PRI_REGEXP);
	}
	else { $endexpr=1; }
      }
      elsif ($c1 eq "=") {
	if ($level <= $PRI_CMP) {
	  $ctxt->{evp}++;
	  process_binary_op($ctxt, "!=", $PRI_CMP);
	}
	else { $endexpr=1; }	
      }
      else {
	$ctxt->{errstr} = "Unexpected operator: !";
	$endexpr=1;
      }
    }
    elsif ($c eq "<") {
      if (nxchar1($ctxt) eq "=") {
	if ($level <= $PRI_CMP) {
	  $ctxt->{evp}++;
	  process_binary_op($ctxt, "<=", $PRI_CMP);
	}
	else { $endexpr=1; }
      }
      else {
	if ($level <= $PRI_CMP) {
	  process_binary_op($ctxt, "<", $PRI_CMP);
	}
	else { $endexpr=1; }
      }
    }
    elsif ($c eq ">") {
      if (nxchar1($ctxt) eq "=") {
	if ($level <= $PRI_CMP) {
	  $ctxt->{evp}++;
	  process_binary_op($ctxt, ">=", $PRI_CMP);
	}
	else { $endexpr=1; }
      }
      else {
	if ($level <= $PRI_CMP) {
	  process_binary_op($ctxt, ">", $PRI_CMP);
	}
	else { $endexpr=1; }
      }
    }
    elsif ($c eq "(") {
      # functions
      my $last_elt=@{$ctxt->{evstack}};
      my $pv=@{$ctxt->{evstack}}[$last_elt-1];
      if (@{$pv}[1] == $TYPE_FUNC) {
	$ctxt->{evp}++;
	$ctxt->{npar}++;
	# zero or one-arg only functions until now
	if (eval_expr(0, $ctxt)) {
	  $c=nxchar($ctxt);
	  if ($c ne ")") {
	    $ctxt->{errstr}="Closing parenthesis expected";
	    $endexpr=1;
	  }
	  else {
	    $ctxt->{npar}--;
	    $ctxt->{evp}++;
	    my ($v,$t);
	    if ($last_elt != @{$ctxt->{evstack}}) {
	      # if there is an argument (the stack is deeper than before evaluating
	      # arguments), then pop it
	      my $pv=pop(@{$ctxt->{evstack}});
	      ($v,$t)=@{$pv};
	    }
	    $pv=pop(@{$ctxt->{evstack}});
	    my ($func_name,$tfn)=@{$pv};
	    my $f=$eval_funcs{$func_name};
	    if (defined($f)) {
	      my $fn=$f->{func};
	      $v=&$fn($ctxt,$v,$t);
	      # functions are all prototyped func(string) at the moment
	      push(@{$ctxt->{evstack}}, [$v, $f->{return_type}]);
	    }
	    else {
	      $ctxt->{errstr} = "Function not found: $func_name";
	    }
	  }
	}
      }
      else {
	$ctxt->{errstr} = "Unexpected open parenthesis";
      }
    }
    else {
      $ctxt->{errstr} = "Unexpected operator";
    }
  }

  return undef if (defined($ctxt->{errstr}));
  1;
}

sub process_filter_mimeobj {
  my ($filter_expr, $top, $res, $mail_ctxt, $dbh, $exprs, $cond_name)=@_;
  my %h;
  $filter_exprs=$exprs;
  $h{mime_obj} = $top;
  $h{evp}=0;
  $h{errstr}=undef;
  $h{npar}=0;
  $h{expr}=$filter_expr;
  $h{len}=length($filter_expr);
  push @{$h{call_stack}}, $cond_name;
  $h{mail_ctxt} = $mail_ctxt; # a hash ref
  $h{mail_id} = $mail_ctxt->{mail_id};
  $h{start_time}=time();
  @{$h{evstack}}=();
  if (eval_expr(0, \%h)) {
    my ($pv2) = pop(@{$h{evstack}});
    my ($v2,$t2)=@{$pv2};
    if (!$v2) {
      $v2=0;
    }
    $$res=$v2;
  }
  else {
    $$res=undef;
    print STDERR "Error $filter_expr: ", $h{errstr}, " at character ", $h{evp}+1, "\n";
    return undef;
  }
  1;
}

sub mimeobj_from_db {
  my ($mail_id,$ctxt)=@_;

  my $sth=$ctxt->{dbh}->prepare("SELECT bodytext FROM body WHERE mail_id=?");
  $sth->execute($mail_id);
  my ($body)=$sth->fetchrow_array;

  $sth=$ctxt->{dbh}->prepare("SELECT lines FROM header WHERE mail_id=?");
  $sth->execute($mail_id);
  my ($headers)=$sth->fetchrow_array;

  my $top = MIME::Entity->build(Encoding => '-SUGGEST',
				Charset => 'iso-8859-1',
				Data => $body);

  for my $hl (split (/\n/, $headers)) {
    $top->head->replace($1, $hl);
  }
  return $top;
}

sub log_filter_hit {
  my ($dbh, $ctxt, $expr_id)=@_;
  my $s1=$dbh->prepare("UPDATE filter_expr SET last_hit=now() WHERE expr_id=?");
  $s1->execute($expr_id);

  if (getconf_bool("log_filter_hits", $ctxt->{mailbox_address})) {
    if (!exists $ctxt->{filters_hit_count}) {
      my $sth=$dbh->prepare("INSERT INTO filter_log(expr_id, mail_id, hit_date) VALUES(?,?,now())");
      $sth->execute($expr_id, $ctxt->{mail_id});
      $ctxt->{filters_hit_count}=1;
    }
    else {
      # When storing several filter hits for the same context (which
      # means for the same message), each hit_date is one microsecond after
      # the previous one (now() being constant during the transaction).
      # This is a kludge that will allow us later to order by hit_date and get the
      # hits in the order in which they happened instead of having an additional
      # int column that would waste space.
      my $sth=$dbh->prepare("INSERT INTO filter_log(expr_id, mail_id, hit_date) VALUES(?,?,now()+?*cast('0.000001s' as interval))");
      $sth->execute($expr_id, $ctxt->{mail_id}, $ctxt->{filters_hit_count});
      $ctxt->{filters_hit_count}++;
    }
  }
}

1;

HTML source code generated by GNU Source-Highlight plus some custom post-processing

List of all available source files