Mail-SpamAssassin
view release on metacpan or search on metacpan
lib/Mail/SpamAssassin/Conf/Parser.pm view on Meta::CPAN
# Strictly controlled form:
if ($token =~ /^(?:\w+::){0,10}\w+$/) {
# trunk Dmarc.pm was renamed to DMARC.pm
# (same check also in Conf.pm loadplugin)
if ($token eq 'Mail::SpamAssassin::Plugin::Dmarc') {
$token = 'Mail::SpamAssassin::Plugin::DMARC';
}
# backwards compatible - removed in 4.1
# (same check also in Conf.pm loadplugin)
elsif ($token eq 'Mail::SpamAssassin::Plugin::WhiteListSubject') {
$token = 'Mail::SpamAssassin::Plugin::WelcomeListSubject';
}
my $u = untaint_var($token);
$eval .= "'$u'";
} else {
my $msg = "config: not allowed value '$token' ".
"in $self->{currentfile} (line $self->{linenum}{$self->{currentfile}})";
$self->lint_warn($msg, undef);
return;
}
}
else {
my $msg = "config: unparseable value '$token' ".
"in $self->{currentfile} (line $self->{linenum}{$self->{currentfile}})";
$self->lint_warn($msg, undef);
return;
}
}
push (@{$if_stack_ref}, {
'type' => $key,
'conditional' => $value,
'skip_parsing' => $$skip_parsing_ref,
'linenum' => $self->{linenum}{$self->{currentfile}}
});
if (eval $eval) {
$self->{cond_cache}{"$key $value"} = 1;
# leave $skip_parsing as-is; we may not be parsing anyway in this block.
# in other words, support nested 'if's and 'require_version's
} else {
if ($@) {
my $msg = "config: error parsing conditional ".
"in $self->{currentfile} (line $self->{linenum}{$self->{currentfile}}): $eval ($@)";
warn $msg;
$self->lint_warn($msg, undef, 0); # not fatal?
}
$self->{cond_cache}{"$key $value"} = 0;
$$skip_parsing_ref = 1;
}
}
# functions supported in the "if" eval:
sub cond_clause_plugin_loaded {
return 1 if $_[1] eq 'Mail::SpamAssassin::Plugin::RaciallyCharged'; # removed in 4.1
return $_[0]->{conf}->{plugins_loaded}->{$_[1]};
}
sub cond_clause_can {
my ($self, $method) = @_;
if ($self->{currentfile} =~ q!\buser_prefs$! ) {
warn "config: 'if can $method' not available in user_prefs";
return 0
}
$self->cond_clause_can_or_has('can', $method);
}
sub cond_clause_has {
my ($self, $method) = @_;
$self->cond_clause_can_or_has('has', $method);
}
sub cond_clause_can_or_has {
my ($self, $fn_name, $method) = @_;
local($1,$2);
if (!defined $method) {
my $msg = "config: bad 'if' line, no argument to $fn_name() ".
"in $self->{currentfile} (line $self->{linenum}{$self->{currentfile}})";
$self->lint_warn($msg, undef);
} elsif ($method =~ /^(.*)::([^:]+)$/) {
no strict "refs";
my($module, $meth) = ($1, $2);
return 1 if $module->can($meth) &&
( $fn_name eq 'has' || &{$method}() );
} else {
my $msg = "config: bad 'if' line, cannot find '::' in $fn_name($method) ".
"in $self->{currentfile} (line $self->{linenum}{$self->{currentfile}})";
$self->lint_warn($msg, undef);
}
return;
}
# Let's do some linting here ...
# This is called from _parse(), BTW, so we can check for $conf->{tests}
# easily before finish_parsing() is called and deletes it.
#
sub lint_check {
my ($self) = @_;
my $conf = $self->{conf};
if ($conf->{lint_rules}) {
# Check for description and score issues in lint fashion
while ( my $k = each %{$conf->{descriptions}} ) {
if (!exists $conf->{tests}->{$k}) {
dbg("config: description exists for non-existent rule $k");
}
}
while ( my($sk) = each %{$conf->{scores}} ) {
if (!exists $conf->{tests}->{$sk}) {
# bug 5514: not a lint warning any more
dbg("config: score set for non-existent rule $sk");
}
}
}
}
# Iterate through tests and check/fix things
sub fix_tests {
my ($self) = @_;
lib/Mail/SpamAssassin/Conf/Parser.pm view on Meta::CPAN
$self->lint_warn("config: invalid head test $name: $text");
return;
}
my ($hdr, $op, $pat) = ($1, $2, $3);
$hdr =~ s/:$//;
if ($hdr =~ /:(?!(?:raw|addr|name|host|domain|ip|revip|first|last)\b)/i) {
$self->lint_warn("config: invalid header modifier for $name: $hdr", $name);
return;
}
if ($pat =~ s/\s+\[if-unset:\s+(.+)\]$//) {
$conf->{test_opt_unset}->{$name} = $1;
}
$self->parse_captures($name, \$pat);
my ($rec, $err) = compile_regexp($pat, 1, $ignore_amre);
if (!$rec) {
$self->lint_warn("config: invalid regexp for $name '$pat': $err", $name);
return;
}
$conf->{test_qrs}->{$name} = $rec;
$conf->{test_opt_header}->{$name} = $hdr;
$conf->{test_opt_neg}->{$name} = 1 if $op eq '!~';
}
}
elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS)
{
if ($self->is_meta_valid($name, $text)) {
# Untaint now once and not repeatedly later
$text = untaint_var($text);
} else {
return;
}
}
elsif (($type & 1) == 1) { # *_EVALS
# create eval_to_rule mappings
if (my ($function) = ($text =~ m/(.*?)\s*\(.*?\)\s*$/)) {
push @{$conf->{eval_to_rule}->{$function}}, $name;
}
}
$conf->{tests}->{$name} = $text;
$conf->{test_types}->{$name} = $type;
if ($name =~ /^AUTOLEARNTEST/) {
dbg("config: auto-learn: $name has type $type = $conf->{test_types}->{$name} during add_test\n");
}
$conf->{priority}->{$name} ||= 0;
if ($conf->{main}->{keep_config_parsing_metadata}) {
# {source_file} eats lots of memory and is unused unless
# keep_config_parsing_metadata is set (ruleqa stuff)
$conf->{source_file}->{$name} = $self->{currentfile};
$conf->{if_stack}->{$name} = $self->get_if_stack_as_string();
if ($self->{file_scoped_attrs}->{testrules}) {
$conf->{testrules}->{$name} = 1; # used in build/mkupdates/listpromotable
}
}
# if we found this rule in a user_prefs file, it's a user rule -- note that
# we may need to recompile the rule code for this type (if they've already
# been compiled, e.g. in spamd).
#
# Note: the want_rebuild_for_type 'flag' is actually a counter; it is decremented
# after each scan. This ensures that we always recompile at least once more;
# once to *define* the rule, and once afterwards to *undefine* the rule in the
# compiled ruleset again.
#
# If two consecutive scans use user rules, that's ok -- the second one will
# reset the counter, and we'll still recompile just once afterwards to undefine
# the rule again.
#
if ($self->{scoresonly}) {
$conf->{want_rebuild_for_type}->{$type} = 2;
$conf->{user_defined_rules}->{$name} = 1;
}
}
sub add_regression_test {
my ($self, $name, $ok_or_fail, $string) = @_;
my $conf = $self->{conf};
if ($conf->{regression_tests}->{$name}) {
push @{$conf->{regression_tests}->{$name}}, [$ok_or_fail, $string];
}
else {
# initialize the array, and create one element
$conf->{regression_tests}->{$name} = [ [$ok_or_fail, $string] ];
}
}
sub is_meta_valid {
my ($self, $name, $rule) = @_;
# $meta is a degenerate translation of the rule, replacing all variables (i.e. rule names) with 0.
my $meta = '';
# Paranoid check (Bug #7557)
if ($rule =~ /(?:\:\:|->|[\$\@\%\;\{\}])/) {
warn("config: invalid meta $name rule: $rule\n");
return 0;
}
# Process expandable functions before lexing
$rule =~ s/${META_RULES_MATCHING_RE}/ 0 /g;
# Lex the rule into tokens using a rather simple RE method ...
my @tokens = ($rule =~ /($ARITH_EXPRESSION_LEXER)/og);
if (length($name) == 1) {
for (@tokens) {
print "$name $_\n " or die "Error writing token: $!";
}
}
# Go through each token in the meta rule
foreach my $token (@tokens) {
# If the token is a syntactically legal rule name, make it zero
if ($token =~ IS_RULENAME) {
$meta .= "0 ";
}
( run in 0.774 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )