spamassassin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j.@apache.org
Subject svn commit: rev 6250 - in incubator/spamassassin/trunk: . lib/Mail lib/Mail/SpamAssassin
Date Fri, 23 Jan 2004 06:01:24 GMT
Author: jm
Date: Thu Jan 22 22:01:21 2004
New Revision: 6250

Added:
   incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm
   incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PluginHandler.pm
Modified:
   incubator/spamassassin/trunk/MANIFEST
   incubator/spamassassin/trunk/MANIFEST.SKIP
   incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm
   incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
   incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
Log:
plugin support, using new loadplugin configuration command

Modified: incubator/spamassassin/trunk/MANIFEST
==============================================================================
--- incubator/spamassassin/trunk/MANIFEST	(original)
+++ incubator/spamassassin/trunk/MANIFEST	Thu Jan 22 22:01:21 2004
@@ -240,3 +240,5 @@
 tools/split_corpora
 tools/test_extract
 tools/triplets.pl
+lib/Mail/SpamAssassin/Plugin.pm
+lib/Mail/SpamAssassin/PluginHandler.pm

Modified: incubator/spamassassin/trunk/MANIFEST.SKIP
==============================================================================
--- incubator/spamassassin/trunk/MANIFEST.SKIP	(original)
+++ incubator/spamassassin/trunk/MANIFEST.SKIP	Thu Jan 22 22:01:21 2004
@@ -111,3 +111,4 @@
 tasks/.*
 build/2.60_change_summary
 build/replace_license_blocks
+sa-learn

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm	(original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm	Thu Jan 22 22:01:21 2004
@@ -113,6 +113,7 @@
 use Mail::SpamAssassin::PerMsgStatus;
 use Mail::SpamAssassin::MsgParser;
 use Mail::SpamAssassin::Bayes;
+use Mail::SpamAssassin::PluginHandler;
 
 use File::Basename;
 use File::Path;
@@ -310,6 +311,7 @@
   $DEBUG->{rulesrun}=64;
 
   $self->{conf} ||= new Mail::SpamAssassin::Conf ($self);
+  $self->{plugins} = Mail::SpamAssassin::PluginHandler->new ($self);
 
   $self->{save_pattern_hits} ||= 0;
 
@@ -1038,11 +1040,13 @@
   my $text = join ('',<IN>);
   close IN;
 
+  $self->{conf}->{main} = $self;
   $self->{conf}->parse_scores_only ($text);
   if ($self->{conf}->{allow_user_rules}) {
       dbg("finishing parsing!");
       $self->{conf}->finish_parsing();
   }
+  delete $self->{conf}->{main};	# to allow future GC'ing
 }
 
 ###########################################################################
@@ -1232,8 +1236,10 @@
     warn "No configuration text or files found! Please check your setup.\n";
   }
 
+  $self->{conf}->{main} = $self;
   $self->{conf}->parse_rules ($self->{config_text});
   $self->{conf}->finish_parsing ();
+  delete $self->{conf}->{main};	# to allow future GC'ing
 
   delete $self->{config_text};
 
@@ -1453,6 +1459,14 @@
   closedir SA_CF_DIR;
 
   return map { "$dir/$_" } sort { $a cmp $b } @cfs;	# sort numerically
+}
+
+###########################################################################
+
+sub call_plugins {
+  my $self = shift;
+  my $subname = shift;
+  return $self->{plugins}->callback ($subname, @_);
 }
 
 ###########################################################################

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm	(original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm	Thu Jan 22 22:01:21 2004
@@ -201,6 +201,8 @@
   $self->{rawbody_evals} = { };
   $self->{meta_tests} = { };
 
+  $self->{eval_plugins} = { };
+
   # testing stuff
   $self->{regression_tests} = { };
 
@@ -1986,7 +1988,16 @@
       $self->{num_check_received} = $1+0; next;
     }
 
+###########################################################################
 
+    if ($self->{main}->call_plugins ("parse_config", {
+		line => $_,
+		user_config => $scoresonly
+	    }))
+    {
+      # a plugin dealt with it successfully.
+      next;
+    }
 
 ###########################################################################
     # SECURITY: no eval'd code should be loaded before this line.
@@ -2643,6 +2654,20 @@
     # user_scores_sql_table here.  All just take \S+ and set the string of the
     # same name on $self.
 
+=item loadplugin PluginModuleName /path/to/module.pm
+
+Load a SpamAssassin plugin module.  The C<PluginModuleName> is the perl module
+name, used to create the plugin object itself; C</path/to/module.pm> is the
+file to load, containing the module's perl code.
+
+See C<Mail::SpamAssassin::Plugin> for more details on writing plugins.
+
+=cut
+
+    if (/^loadplugin\s+(\S+)\s+(\S+)$/) {
+      $self->load_plugin ($1, $2); next;
+    }
+
 ###########################################################################
 
 failed_line:
@@ -2866,6 +2891,18 @@
   }
 
   return 0;
+}
+
+###########################################################################
+
+sub load_plugin {
+  my ($self, $package, $path) = @_;
+  $self->{main}->{plugins}->load_plugin ($package, $path);
+}
+
+sub register_eval_rule {
+  my ($self, $pluginobj, $nameofsub) = @_;
+  $self->{eval_plugins}->{$nameofsub} = $pluginobj;
 }
 
 ###########################################################################

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm	(original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm	Thu Jan 22 22:01:21
2004
@@ -2174,6 +2174,17 @@
     my ($function, @args) = @{$test};
     unshift(@args, @extraevalargs);
 
+    # check to make sure the function is defined
+    if (!$self->can ($function)) {
+      my $pluginobj = $self->{conf}->{eval_plugins}->{$function};
+      if ($pluginobj) {
+	# we have a plugin for this.  eval its function
+	$self->register_plugin_eval_glue ($pluginobj, $function);
+      } else {
+	dbg ("no method found for eval test $function");
+      }
+    }
+
     eval {
       $result = $self->$function(@args);
     };
@@ -2191,6 +2202,31 @@
     } else {
         #dbg("Ran run_eval_test rule $rulename but did not get hit", "rulesrun", 32) if $debugenabled;
     }
+  }
+}
+
+sub register_plugin_eval_glue {
+  my ($self, $pluginobj, $function) = @_;
+
+  dbg ("registering glue method for $function ($pluginobj)");
+  my $evalstr = <<"ENDOFEVAL";
+{
+    package Mail::SpamAssassin::PerMsgStatus;
+
+	sub $function {
+	  my (\$self) = shift;
+	  my \$plugin = \$self->{conf}->{eval_plugins}->{$function};
+	  return \$plugin->$function (\$self, \@_);
+	}
+
+	1;
+}
+ENDOFEVAL
+  eval $evalstr;
+
+  if ($@) {
+    warn "Failed to run header SpamAssassin tests, skipping some: $@\n";
+    $self->{rule_errors}++;
   }
 }
 

Added: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm
==============================================================================
--- (empty file)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm	Thu Jan 22 22:01:21 2004
@@ -0,0 +1,309 @@
+# <@LICENSE>
+# ====================================================================
+# The Apache Software License, Version 1.1
+# 
+# Copyright (c) 2000 The Apache Software Foundation.  All rights
+# reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+# 
+# 3. The end-user documentation included with the redistribution,
+#    if any, must include the following acknowledgment:
+#       "This product includes software developed by the
+#        Apache Software Foundation (http://www.apache.org/)."
+#    Alternately, this acknowledgment may appear in the software itself,
+#    if and wherever such third-party acknowledgments normally appear.
+# 
+# 4. The names "Apache" and "Apache Software Foundation" must
+#    not be used to endorse or promote products derived from this
+#    software without prior written permission. For written
+#    permission, please contact apache@apache.org.
+# 
+# 5. Products derived from this software may not be called "Apache",
+#    nor may "Apache" appear in their name, without prior written
+#    permission of the Apache Software Foundation.
+# 
+# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+# ====================================================================
+# 
+# This software consists of voluntary contributions made by many
+# individuals on behalf of the Apache Software Foundation.  For more
+# information on the Apache Software Foundation, please see
+# <http://www.apache.org/>.
+# 
+# Portions of this software are based upon public domain software
+# originally written at the National Center for Supercomputing Applications,
+# University of Illinois, Urbana-Champaign.
+# </@LICENSE>
+
+=head1 NAME
+
+Mail::SpamAssassin::Plugin - SpamAssassin plugin base class
+
+=head1 SYNOPSIS
+
+  package MyPlugin;
+
+  use Mail::SpamAssassin::Plugin;
+  use vars qw(@ISA);
+  @ISA = qw(Mail::SpamAssassin::Plugin);
+
+  sub new {
+    my $class = shift;
+    my $mailsaobject = shift;
+    
+    # the usual perlobj boilerplate to create a subclass object
+    $class = ref($class) || $class;
+    my $self = $class->SUPER::new($mailsaobject);
+    bless ($self, $class);
+   
+    # then register an eval rule
+    $self->register_eval_rule ("check_for_foo");
+
+    # and return the new plugin object
+    return $self;
+  }
+
+  ...methods...
+
+  1;
+
+=head1 DESCRIPTION
+
+This is the base class for SpamAssassin plugins; all plugins must be objects
+that implement this class.
+
+This class provides no-op stub methods for all the callbacks that a plugin
+can receive.  It is expected that your plugin will override one or more
+of these stubs to perform its actions.
+
+SpamAssassin implements a plugin chain; each callback event is passed to each
+of the registered plugin objects in turn.  Any plugin can call
+C<$plugin->inhibit_further_callbacks()> to block delivery of that event to
+later plugins in the chain.  This is useful if the plugin has handled the
+event, and there will be no need for later plugins to handle it as well.
+
+The following methods can be overridden by subclasses to handle events
+that SpamAssassin will call back to:
+
+=head1 INTERFACE
+
+=over 4
+
+=cut
+
+package Mail::SpamAssassin::Plugin;
+use Mail::SpamAssassin;
+
+use strict;
+use bytes;
+
+use vars qw{
+  @ISA $VERSION
+};
+
+@ISA = qw();
+$VERSION = 'bogus';
+
+###########################################################################
+
+=item $plugin = MyPluginClass->new ($mailsaobject)
+
+Constructor.  Plugins that need to register themselves will need to
+define their own; the default super-class constructor will work fine
+for plugins that just override a method.
+
+Note that subclasses must provide the C<$mailsaobject> to the
+superclass constructor, like so:
+
+  my $self = $class->SUPER::new($mailsaobject);
+
+=cut
+
+sub new {
+  my $class = shift;
+  my $mailsaobject = shift;
+  $class = ref($class) || $class;
+
+  if (!defined $mailsaobject) {
+    die "plugin: usage: Mail::SpamAssassin::Plugin::new(class,mailsaobject)";
+  }
+
+  my $self = {
+    main => $mailsaobject,
+    _inhibit_further_callbacks => 0
+  };
+  bless ($self, $class);
+  $self;
+}
+
+=item $plugin->parse_config ( { options ... } )
+
+Parse a configuration line that hasn't already been handled.  C<options>
+is a reference to a hash containing these options:
+
+=over 4
+
+=item line
+
+The line of configuration text to parse.   This has leading and trailing
+whitespace, and comments, removed.
+
+=item user_config
+
+A boolean: C<1> if reading a user's configuration, C<0> if reading the
+system-wide configuration files.
+
+=back
+
+If the configuration line was a setting that is handled by this plugin, the
+method implementation should call C<$plugin->inhibit_further_callbacks()> and
+return C<1>.
+
+If the setting is not handled by this plugin, the method should return C<0> so
+that a later plugin may handle it, or so that SpamAssassin can output a warning
+message to the user if no plugin understands it.
+
+Note that it is suggested that configuration be stored on the
+C<Mail::SpamAssassin::Conf> object in use, instead of the plugin object itself.
+That can be found as C<$plugin->{main}->{conf}>.
+
+=cut
+
+sub parse_config {
+  my ($self, $opts) = @_;
+  # implemented by subclasses, no-op by default
+  return 0;
+}
+
+=item $plugin->finish ()
+
+Called when the C<Mail::SpamAssassin> object is destroyed.
+
+=cut
+
+sub finish {
+  my ($self) = @_;
+  # implemented by subclasses, no-op by default
+}
+
+###########################################################################
+
+=back
+
+=head1 HELPER APIS
+
+These methods provide an API for plugins to register themselves
+to receive specific events, or control the callback chain behaviour.
+
+=over 4
+
+=item $plugin->register_eval_rule ($nameofevalsub)
+
+Plugins that implement an eval test will need to call this, so that
+SpamAssassin calls into the object when that eval test is encountered.
+
+For example,
+
+  $plugin->register_eval_rule ('check_for_foo')
+
+will cause C<$plugin->check_for_foo()> to be called for this
+SpamAssassin rule:
+
+  header   FOO_RULE	eval:check_for_foo()
+
+Note that eval rules are passed the following arguments:
+
+=over 4
+
+=item The plugin object itself
+
+=item The C<Mail::SpamAssassin::PerMsgStatus> object calling the rule
+
+=item any and all arguments specified in the configuration file
+
+=back
+
+In other words, the eval test method should look something like this:
+
+  sub check_for_foo {
+    my ($self, $permsgstatus, ...arguments...) = @_;
+    ...code returning 0 or 1
+  }
+
+Note that the headers can be accessed using the C<get()> method on the
+C<Mail::SpamAssassin::PerMsgStatus> object, and the body by
+C<get_decoded_stripped_body_text_array()> and other similar methods.
+Similarly, the C<Mail::SpamAssassin::Conf> object holding the current
+configuration may be accessed through C<$permsgstatus->{main}->{conf}>.
+
+The eval rule should return C<1> for a hit, or C<0> if the rule
+is not hit.
+
+State for a single message being scanned should be stored on the C<$checker>
+object, not on the C<$self> object, since C<$self> persists between scan
+operations.
+
+=cut
+
+sub register_eval_rule {
+  my ($self, $nameofsub) = @_;
+  $self->{main}->{conf}->register_eval_rule ($self, $nameofsub);
+}
+
+=item $plugin->inhibit_further_callbacks()
+
+Tells the plugin handler to inhibit calling into other plugins in the plugin
+chain for the current callback.  Frequently used when parsing configuration
+settings using C<parse_config()>.
+
+=cut
+
+sub inhibit_further_callbacks {
+  my ($self) = @_;
+  $self->{_inhibit_further_callbacks} = 1;
+}
+
+=item dbg ($message)
+
+Output a debugging message C<$message>, if the SpamAssassin object is running
+with debugging turned on.
+
+=cut
+
+sub dbg { Mail::SpamAssassin::dbg (@_); }
+
+1;
+
+=back
+
+=head1 SEE ALSO
+
+C<Mail::SpamAssassin>
+
+C<Mail::SpamAssassin::PerMsgStatus>
+
+http://bugzilla.spamassassin.org/show_bug.cgi?id=2163
+
+=cut

Added: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PluginHandler.pm
==============================================================================
--- (empty file)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PluginHandler.pm	Thu Jan 22 22:01:21
2004
@@ -0,0 +1,162 @@
+# <@LICENSE>
+# ====================================================================
+# The Apache Software License, Version 1.1
+# 
+# Copyright (c) 2000 The Apache Software Foundation.  All rights
+# reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+# 
+# 3. The end-user documentation included with the redistribution,
+#    if any, must include the following acknowledgment:
+#       "This product includes software developed by the
+#        Apache Software Foundation (http://www.apache.org/)."
+#    Alternately, this acknowledgment may appear in the software itself,
+#    if and wherever such third-party acknowledgments normally appear.
+# 
+# 4. The names "Apache" and "Apache Software Foundation" must
+#    not be used to endorse or promote products derived from this
+#    software without prior written permission. For written
+#    permission, please contact apache@apache.org.
+# 
+# 5. Products derived from this software may not be called "Apache",
+#    nor may "Apache" appear in their name, without prior written
+#    permission of the Apache Software Foundation.
+# 
+# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+# ====================================================================
+# 
+# This software consists of voluntary contributions made by many
+# individuals on behalf of the Apache Software Foundation.  For more
+# information on the Apache Software Foundation, please see
+# <http://www.apache.org/>.
+# 
+# Portions of this software are based upon public domain software
+# originally written at the National Center for Supercomputing Applications,
+# University of Illinois, Urbana-Champaign.
+# </@LICENSE>
+
+=head1 NAME
+
+Mail::SpamAssassin::PluginHandler - SpamAssassin plugin handler
+
+=cut
+
+package Mail::SpamAssassin::PluginHandler;
+use Mail::SpamAssassin;
+use Mail::SpamAssassin::Plugin;
+
+use strict;
+use bytes;
+
+use vars qw{
+  @ISA $VERSION
+};
+
+@ISA = qw();
+
+$VERSION = 'bogus';     # avoid CPAN.pm picking up version strings later
+
+###########################################################################
+
+sub new {
+  my $class = shift;
+  my $main = shift;
+  $class = ref($class) || $class;
+  my $self = {
+    plugins		=> [ ],
+    main		=> $main
+  };
+  bless ($self, $class);
+  $self;
+}
+
+###########################################################################
+
+sub load_plugin {
+  my ($self, $package, $path) = @_;
+
+  dbg ("plugin: loading $path");
+
+  if (!do $path) {
+    if ($@) { warn "failed to parse plugin $path: $@\n"; }
+    elsif ($!) { warn "failed to load plugin $path: $!\n"; }
+  }
+
+  my $plugin = eval $package.q{->new ($self->{main}); };
+
+  if ($@ || !$plugin) { warn "failed to create plugin $package: $@\n"; }
+
+  if ($plugin) {
+    $self->{main}->{plugins}->register_plugin ($plugin);
+  }
+}
+
+sub register_plugin {
+  my ($self, $plugin) = @_;
+  $plugin->{main} = $self->{main};
+  push (@{$self->{plugins}}, $plugin);
+  dbg ("plugin: registered $plugin");
+}
+
+###########################################################################
+
+sub callback {
+  my $self = shift;
+  my $subname = shift;
+  my $ret;
+
+  foreach my $plugin (@{$self->{plugins}}) {
+    $plugin->{_inhibit_further_callbacks} = 0;
+
+    dbg ("plugin: calling $subname on $plugin");
+    my $methodref = $plugin->can ($subname);
+    $ret = &$methodref ($plugin, @_);
+
+    if ($plugin->{_inhibit_further_callbacks}) {
+      dbg ("plugin: $plugin inhibited further callbacks");
+      last;
+    }
+  }
+
+  return $ret;
+}
+
+###########################################################################
+
+sub finish {
+  my $self = shift;
+  foreach my $plugin (@{$self->{plugins}}) {
+    $plugin->finish();
+    delete $plugin->{main};
+  }
+  delete $self->{plugins};
+  delete $self->{main};
+}
+
+###########################################################################
+
+sub dbg { Mail::SpamAssassin::dbg (@_); }
+
+1;

Mime
View raw message