svn commit: r1935656 - in spamassassin/trunk: . lib/Mail/SpamAssassin lib/Mail/SpamAssassin/Conf t/data

[email protected] Fri, 26 Jun 2026 04:24:08 -0000
Newsgroups gmane.mail.spam.spamassassin.cvs
Message-ID <178244784818.1378021.7774844089280520594@svn03-he-fi>
Author: fkento
Date: Fri Jun 26 04:24:07 2026
New Revision: 1935656

Log:
Add Mail::SpamAssassin::Handler base class and loadhandler/tryhandler/ifhandler directives

Modified:
   spamassassin/trunk/MANIFEST
   spamassassin/trunk/UPGRADE
   spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
   spamassassin/trunk/lib/Mail/SpamAssassin/Conf/Parser.pm
   spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm
   spamassassin/trunk/t/data/testhandler.pm

Modified: spamassassin/trunk/MANIFEST
==============================================================================
--- spamassassin/trunk/MANIFEST	Fri Jun 26 04:20:06 2026	(r1935655)
+++ spamassassin/trunk/MANIFEST	Fri Jun 26 04:24:07 2026	(r1935656)
@@ -53,6 +53,7 @@ lib/Mail/SpamAssassin/FuzzyHash.pm
 lib/Mail/SpamAssassin/FuzzyHash/Util.pm
 lib/Mail/SpamAssassin/FuzzyHash/ZOrder.pm
 lib/Mail/SpamAssassin/GeoDB.pm
+lib/Mail/SpamAssassin/Handler.pm
 lib/Mail/SpamAssassin/Header.pm
 lib/Mail/SpamAssassin/Header/ArcAuthenticationResults.pm
 lib/Mail/SpamAssassin/Header/AuthenticationResults.pm
@@ -580,6 +581,7 @@ t/fromnamespoof.t
 t/get_all_headers.t
 t/get_headers.t
 t/gtube.t
+t/handler_aliases.t
 t/header.t
 t/header_utf8.t
 t/hashbl.t

Modified: spamassassin/trunk/UPGRADE
==============================================================================
--- spamassassin/trunk/UPGRADE	Fri Jun 26 04:20:06 2026	(r1935655)
+++ spamassassin/trunk/UPGRADE	Fri Jun 26 04:24:07 2026	(r1935656)
@@ -68,16 +68,21 @@ Note for Users Upgrading to SpamAssassin
   server in order to catch more redirectors that uses Javascript or
   other tricks.
 
-- New MIME-part handler framework. Plugins can now register a method
-  as the handler for a content-type pattern (an exact type such as
-  "image/jpeg" or a major-type glob such as "image/*") via
-  $plugin->register_handler(). During message processing, each matching
+- New MIME-part handler framework. A handler is a subclass of the new
+  Mail::SpamAssassin::Handler class (itself a subclass of
+  Mail::SpamAssassin::Plugin) that registers one of its methods as the
+  handler for a content-type pattern -- an exact type such as
+  "image/jpeg" or a major-type glob such as "image/*" -- via
+  $self->register_handler(). During message processing, each matching
   MIME part is dispatched to its handler, which can inject extracted
   text into the part and return synthetic child parts that are
   dispatched recursively (for example, nested archives or embedded
   images). Dispatch is bounded by the new handler_max_depth,
   handler_max_parts, handler_max_bytes, and handler_time_limit options
-  to guard against deeply nested or oversized content.
+  to guard against deeply nested or oversized content. Handlers are
+  loaded with the new loadhandler and tryhandler directives, and a
+  block of configuration can be made conditional on a handler with
+  ifhandler.
 
 Note for Users Upgrading to SpamAssassin 4.0.2
 ----------------------------------------------

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm	Fri Jun 26 04:20:06 2026	(r1935655)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm	Fri Jun 26 04:24:07 2026	(r1935656)
@@ -4492,6 +4492,68 @@ the filesystem.
     }
   });
 
+=item loadhandler Mail::SpamAssassin::Handler::ModuleName [/path/module.pm]
+
+Load a SpamAssassin MIME-part handler module.  A handler is a subclass of
+L<Mail::SpamAssassin::Handler> that registers itself for one or more content
+types with C<register_handler> and is invoked once per matching MIME part.
+
+The module name must be given in full; unlike a bare word it is not prefixed
+automatically.  C<ModuleName> and the optional C</path/module.pm> are otherwise
+interpreted exactly as for C<loadplugin>.
+
+=cut
+
+  push (@cmds, {
+    setting => 'loadhandler',
+    is_admin => 1,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      if ($value eq '') {
+        return $MISSING_REQUIRED_VALUE;
+      }
+      my ($package, $path);
+      local ($1,$2);
+      if ($value =~ /^((?:\w+::){0,10}\w+)(?:\s+(\S+\.pm))?$/i) {
+        ($package, $path) = ($1, $2);
+      } else {
+	return $INVALID_VALUE;
+      }
+      # A handler is a Mail::SpamAssassin::Plugin subclass, so loadhandler is an
+      # alias for loadplugin: load it through the same path.  Keeping a separate
+      # directive lets handler config stay decoupled from the loading mechanism
+      # in case the two diverge in future.
+      $self->load_plugin ($package, $path);
+    }
+  });
+
+=item tryhandler Mail::SpamAssassin::Handler::ModuleName [/path/module.pm]
+
+Same as C<loadhandler>, but silently ignored if the .pm file cannot be found in
+the filesystem.
+
+=cut
+
+  push (@cmds, {
+    setting => 'tryhandler',
+    is_admin => 1,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      if ($value eq '') {
+        return $MISSING_REQUIRED_VALUE;
+      }
+      my ($package, $path);
+      local ($1,$2);
+      if ($value =~ /^((?:\w+::){0,10}\w+)(?:\s+(\S+\.pm))?$/i) {
+        ($package, $path) = ($1, $2);
+      } else {
+	return $INVALID_VALUE;
+      }
+      # tryhandler is to loadhandler as tryplugin is to loadplugin: load silently.
+      $self->load_plugin ($package, $path, 1);
+    }
+  });
+
 =item handler_max_depth n               (default: 8)
 
 Maximum handler chain depth (guards against deeply nested archives).
@@ -4855,6 +4917,12 @@ For example:
 
 An alias for C<if plugin(PluginModuleName)>.
 
+=item ifhandler HandlerModuleName
+
+Include the configuration up to the matching C<endif> only if the named handler
+module has been loaded.  The counterpart of C<ifplugin> for handlers loaded with
+C<loadhandler>.
+
 =item else
 
 Used to support conditional interpretation of the configuration
@@ -5522,7 +5590,7 @@ sub load_plugin {
 }
 
 # Register a plugin method as the MIME-part handler for a content-type pattern.
-# Called via Mail::SpamAssassin::Plugin::register_handler.  $pattern is an exact
+# Called via Mail::SpamAssassin::Handler::register_handler.  $pattern is an exact
 # type ("image/jpeg") or a major-type glob ("image/*"); $method is the name of
 # the method to invoke on $obj for each matching part.
 sub register_handler {

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Conf/Parser.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Conf/Parser.pm	Fri Jun 26 04:20:06 2026	(r1935655)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Conf/Parser.pm	Fri Jun 26 04:24:07 2026	(r1935656)
@@ -300,7 +300,9 @@ sub parse {
       $skip_parsing = $lastcond->{skip_parsing};
       next;
     }
-    elsif ($key eq 'ifplugin') {
+    elsif ($key eq 'ifplugin' || $key eq 'ifhandler') {
+      # ifhandler is an alias for ifplugin: a handler is a Plugin subclass, so it
+      # is recorded in plugins_loaded and satisfied by the plugin() conditional.
       if ($value eq '') {
         $parse_error = "config: missing '$key' condition";
         goto failed_line;

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm	Fri Jun 26 04:20:06 2026	(r1935655)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm	Fri Jun 26 04:24:07 2026	(r1935656)
@@ -1069,29 +1069,6 @@ sub register_eval_rule {
   $self->{main}->{conf}->register_eval_rule ($self, $nameofsub, $ruletype);
 }
 
-=item $plugin-E<gt>register_handler ($mime_pattern, $nameofsub)
-
-Register one of this plugin's methods as the MIME-part handler for a
-content-type pattern.  C<$mime_pattern> is an exact type (C<image/jpeg>) or a
-major-type glob (C<image/*>); the most specific match wins.  C<$nameofsub> is
-the name of a method on this plugin that will be called as
-C<< $plugin->$nameofsub($node, $permsgstatus) >> for each matching MIME part,
-during message metadata extraction (before body rules run and before the URI
-list is frozen).
-
-The method may inject extracted text into the part with
-C<< $node->set_rendered($text, $type) >>, accumulate per-message findings on
-C<$permsgstatus>, and return an arrayref of synthetic child-part specs
-(C<< { type => ..., data => $bytes, name => ... } >>) which are dispatched
-recursively -- or C<undef>/C<[]> for none.
-
-=cut
-
-sub register_handler {
-  my ($self, $mime_pattern, $nameofsub) = @_;
-  $self->{main}->{conf}->register_handler ($self, $mime_pattern, $nameofsub);
-}
-
 =item $plugin-E<gt>register_generated_rule_method ($nameofsub)
 
 In certain circumstances, plugins may find it useful to compile

Modified: spamassassin/trunk/t/data/testhandler.pm
==============================================================================
--- spamassassin/trunk/t/data/testhandler.pm	Fri Jun 26 04:20:06 2026	(r1935655)
+++ spamassassin/trunk/t/data/testhandler.pm	Fri Jun 26 04:24:07 2026	(r1935656)
@@ -19,10 +19,10 @@ package myTestHandler;
 use strict;
 use warnings;
 
-use Mail::SpamAssassin::Plugin;
+use Mail::SpamAssassin::Handler;
 use Mail::SpamAssassin::Logger;
 
-our @ISA = qw(Mail::SpamAssassin::Plugin);
+our @ISA = qw(Mail::SpamAssassin::Handler);
 
 sub new {
   my ($class, $main) = @_;