Index: lib/Mail/SpamAssassin.pm =================================================================== --- lib/Mail/SpamAssassin.pm (revision 227494) +++ lib/Mail/SpamAssassin.pm (working copy) @@ -1608,6 +1608,15 @@ ########################################################################### +sub have_plugin { + my ($self, $subname) = @_; + + # We could potentially get called after a finish(), so just return. + return unless $self->{plugins}; + + return $self->{plugins}->have_callback ($subname); +} + sub call_plugins { my $self = shift; Index: lib/Mail/SpamAssassin/Plugin.pm =================================================================== --- lib/Mail/SpamAssassin/Plugin.pm (revision 227494) +++ lib/Mail/SpamAssassin/Plugin.pm (working copy) @@ -359,6 +359,63 @@ =back +=item $plugin->start_rules ( { options ... } ) + +Called before testing a set of rules of a given type and priority. + +=over 4 + +=item permsgstatus + +The C context object for this scan. + +=item ruletype + +The type of the rules about to be performed. + +=back + +=item $plugin->hit_rule ( { options ... } ) + +Called when a rule fires. + +=over 4 + +=item permsgstatus + +The C context object for this scan. + +=item ruletype + +The type of the rule that fired. + +=item rulename + +The name of the rule that fired. + +=back + +=item $plugin->ran_rule ( { options ... } ) + +Called after a rule has been tested, whether or not it fired. When the +rule fires, the hit_rule callback is always called before this. + +=over 4 + +=item permsgstatus + +The C context object for this scan. + +=item ruletype + +The type of the rule that was tested. + +=item rulename + +The name of the rule that was tested. + +=back + =item $plugin->check_end ( { options ... } ) Signals that a message check operation has just finished, and the Index: lib/Mail/SpamAssassin/PerMsgStatus.pm =================================================================== --- lib/Mail/SpamAssassin/PerMsgStatus.pm (revision 227494) +++ lib/Mail/SpamAssassin/PerMsgStatus.pm (working copy) @@ -1535,10 +1535,20 @@ ########################################################################### -sub ran_rule_debug_code { +sub start_rules_plugin_code { + my ($self, $ruletype) = @_; + + return '' unless $self->{main}->have_plugin("start_rules"); + + return ' + $self->{main}->call_plugins ("start_rules", { permsgstatus => $self, ruletype => \''.$ruletype.'\' }); + '; +} + +sub hit_rule_plugin_code { my ($self, $rulename, $ruletype, $bit) = @_; - return '' unless exists($self->{should_log_rule_hits}); + return '' unless exists($self->{should_log_rule_hits}) || $self->{main}->have_plugin("hit_rule"); # note: keep this in 'single quotes' to avoid the $ & performance hit, # unless specifically requested by the caller. Also split the @@ -1546,6 +1556,15 @@ # doesn't impose that hit anyway (just in case) my $match = '($' . '&' . '|| "negative match")'; + my $debug_code = ''; + if (exists($self->{should_log_rule_hits})) { + $debug_code = ' + dbg("rules: ran '.$ruletype.' rule '.$rulename.' ======> got hit: \"" . '. + $match.' . "\""); + + '; + } + my $save_hits_code = ''; if ($self->{save_pattern_hits}) { $save_hits_code = ' @@ -1553,10 +1572,24 @@ '; } + my $plugin_code = ''; + if ($self->{main}->have_plugin("hit_rule")) { + $plugin_code = ' + $self->{main}->call_plugins ("hit_rule", { permsgstatus => $self, rulename => \''.$rulename.'\', ruletype => \''.$ruletype.'\' }); + '; + } + + return $debug_code.$save_hits_code.$plugin_code.' + '; +} + +sub ran_rule_plugin_code { + my ($self, $rulename, $ruletype) = @_; + + return '' unless $self->{main}->have_plugin("ran_rule"); + return ' - dbg("rules: ran '.$ruletype.' rule '.$rulename.' ======> got hit: \"" . '. - $match.' . "\""); - '.$save_hits_code.' + $self->{main}->call_plugins ("ran_rule", { permsgstatus => $self, rulename => \''.$rulename.'\', ruletype => \''.$ruletype.'\' }); '; } @@ -1595,7 +1628,7 @@ return; } - my $evalstr = ''; + my $evalstr = $self->start_rules_plugin_code("header"); my $evalstr2 = ''; while (my($rulename, $rule) = each %{$self->{conf}{head_tests}->{$priority}}) { @@ -1617,6 +1650,7 @@ $evalstr .= ' if ($self->{conf}->{scores}->{q#'.$rulename.'#}) { '.$rulename.'_head_test($self, $_); # no need for OO calling here (its faster this way) + '.$self->ran_rule_plugin_code($rulename, "header").' } '; @@ -1631,7 +1665,7 @@ '.$self->hash_line_for_rule($rulename).' if ($self->get(q#'.$hdrname.'#, q#'.$def.'#) '.$testtype.'~ '.$pat.') { $self->got_hit (q#'.$rulename.'#, q{}); - '. $self->ran_rule_debug_code($rulename, "header", 1) . ' + '. $self->hit_rule_plugin_code($rulename, "header", 1) . ' } }'; @@ -1696,7 +1730,7 @@ } # build up the eval string... - my $evalstr = ''; + my $evalstr = $self->start_rules_plugin_code("body"); my $evalstr2 = ''; while (my($rulename, $pat) = each %{$self->{conf}{body_tests}->{$priority}}) { @@ -1704,6 +1738,7 @@ if ($self->{conf}->{scores}->{q{'.$rulename.'}}) { # call procedurally as it is faster. '.$rulename.'_body_test($self,@_); + '.$self->ran_rule_plugin_code($rulename, "body").' } '; @@ -1718,7 +1753,7 @@ '.$self->hash_line_for_rule($rulename).' if ('.$pat.') { $self->got_pattern_hit(q{'.$rulename.'}, "BODY: "); - '. $self->ran_rule_debug_code($rulename, "body", 2) . ' + '. $self->hit_rule_plugin_code($rulename, "body", 2) . ' # Ok, we hit, stop now. last; } @@ -2108,13 +2143,15 @@ } # otherwise build up the eval string... - my $evalstr = ''; + my $evalstr = $self->start_rules_plugin_code("uri"); my $evalstr2 = ''; while (my($rulename, $pat) = each %{$self->{conf}{uri_tests}->{$priority}}) { $evalstr .= ' if ($self->{conf}->{scores}->{q{'.$rulename.'}}) { '.$rulename.'_uri_test($self, @_); # call procedurally for speed + '.$self->ran_rule_plugin_code($rulename, "uri").' + } '; @@ -2129,7 +2166,7 @@ '.$self->hash_line_for_rule($rulename).' if ('.$pat.') { $self->got_pattern_hit(q{'.$rulename.'}, "URI: "); - '. $self->ran_rule_debug_code($rulename, "uri", 4) . ' + '. $self->hit_rule_plugin_code($rulename, "uri", 4) . ' # Ok, we hit, stop now. last; } @@ -2197,13 +2234,14 @@ } # build up the eval string... - my $evalstr = ''; + my $evalstr = $self->start_rules_plugin_code("rawbody"); my $evalstr2 = ''; while (my($rulename, $pat) = each %{$self->{conf}{rawbody_tests}->{$priority}}) { $evalstr .= ' if ($self->{conf}->{scores}->{q{'.$rulename.'}}) { '.$rulename.'_rawbody_test($self, @_); # call procedurally for speed + '.$self->ran_rule_plugin_code($rulename, "rawbody").' } '; @@ -2218,7 +2256,7 @@ '.$self->hash_line_for_rule($rulename).' if ('.$pat.') { $self->got_pattern_hit(q{'.$rulename.'}, "RAW: "); - '. $self->ran_rule_debug_code($rulename, "rawbody", 8) . ' + '. $self->hit_rule_plugin_code($rulename, "rawbody", 8) . ' # Ok, we hit, stop now. last; } @@ -2287,7 +2325,7 @@ } # build up the eval string... - my $evalstr = ''; + my $evalstr = $self->start_rules_plugin_code("full"); while (my($rulename, $pat) = each %{$self->{conf}{full_tests}->{$priority}}) { $evalstr .= ' @@ -2295,8 +2333,9 @@ '.$self->hash_line_for_rule($rulename).' if ($$fullmsgref =~ '.$pat.') { $self->got_pattern_hit(q{'.$rulename.'}, "FULL: "); - '. $self->ran_rule_debug_code($rulename, "full", 16) . ' + '. $self->hit_rule_plugin_code($rulename, "full", 16) . ' } + '.$self->ran_rule_plugin_code($rulename, "full").' } '; } Index: lib/Mail/SpamAssassin/PluginHandler.pm =================================================================== --- lib/Mail/SpamAssassin/PluginHandler.pm (revision 227494) +++ lib/Mail/SpamAssassin/PluginHandler.pm (working copy) @@ -130,10 +130,8 @@ ########################################################################### -sub callback { - my $self = shift; - my $subname = shift; - my ($ret, $overallret); +sub have_callback { + my ($self, $subname) = @_; # have we set up the cache entry for this callback type? if (!exists $self->{cbs}->{$subname}) { @@ -150,6 +148,19 @@ $self->{cbs}->{$subname} = \@subs; } + return scalar(@{$self->{cbs}->{$subname}}); +} + +sub callback { + my $self = shift; + my $subname = shift; + my ($ret, $overallret); + + # have we set up the cache entry for this callback type? + if (!exists $self->{cbs}->{$subname}) { + return unless $self->have_callback($subname); + } + foreach my $cbpair (@{$self->{cbs}->{$subname}}) { my ($plugin, $methodref) = @$cbpair;