File Coverage

blib/lib/Mail/SpamAssassin/PluginHandler.pm
Criterion Covered Total %
statement 96 117 82.0
branch 20 38 52.6
condition 8 13 61.5
subroutine 14 15 93.3
pod 0 7 0.0
total 138 190 72.6


line stmt bran cond sub pod time code
1             # <@LICENSE>
2             # Licensed to the Apache Software Foundation (ASF) under one or more
3             # contributor license agreements. See the NOTICE file distributed with
4             # this work for additional information regarding copyright ownership.
5             # The ASF licenses this file to you under the Apache License, Version 2.0
6             # (the "License"); you may not use this file except in compliance with
7             # the License. You may obtain a copy of the License at:
8             #
9             # http://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing, software
12             # distributed under the License is distributed on an "AS IS" BASIS,
13             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14             # See the License for the specific language governing permissions and
15             # limitations under the License.
16             # </@LICENSE>
17              
18             =head1 NAME
19              
20             Mail::SpamAssassin::PluginHandler - SpamAssassin plugin handler
21              
22             =cut
23              
24             package Mail::SpamAssassin::PluginHandler;
25              
26 40     40   334 use Mail::SpamAssassin;
  40         114  
  40         1386  
27 40     40   14556 use Mail::SpamAssassin::Plugin;
  40         142  
  40         1541  
28 40     40   309 use Mail::SpamAssassin::Util;
  40         105  
  40         1675  
29 40     40   287 use Mail::SpamAssassin::Logger;
  40         116  
  40         1795  
30              
31 40     40   253 use strict;
  40         112  
  40         786  
32 40     40   242 use warnings;
  40         101  
  40         984  
33             # use bytes;
34 40     40   232 use re 'taint';
  40         124  
  40         1549  
35 40     40   280 use File::Spec;
  40         100  
  40         43274  
36              
37             our @ISA = qw();
38              
39             #Removed $VERSION per BUG 6422
40             #$VERSION = 'bogus'; # avoid CPAN.pm picking up version strings later
41              
42             # Normally, the list of active plugins that should be called for a given hook
43             # method name is compiled and cached at runtime. This means that later calls
44             # will not have to traverse the entire plugin list more than once, since the
45             # list of plugins that implement that hook is already cached.
46             #
47             # However, some hooks should not receive this treatment. One of these is
48             # parse_config, which may be compiled before all config files have been read;
49             # if a plugin is loaded from a config file after this has been compiled, it
50             # will not get callbacks.
51             #
52             # Any other such hooks that may be compiled at config-parse-time should be
53             # listed here.
54              
55             our @CONFIG_TIME_HOOKS = qw( parse_config );
56              
57             ###########################################################################
58              
59             sub new {
60 81     81 0 332 my $class = shift;
61 81         208 my $main = shift;
62 81   33     619 $class = ref($class) || $class;
63 81         757 my $self = {
64             plugins => [ ],
65             cbs => { },
66             main => $main
67             };
68 81         342 bless ($self, $class);
69 81         299 $self;
70             }
71              
72             ###########################################################################
73              
74             sub load_plugin {
75 4680     4680 0 8851 my ($self, $package, $path, $silent) = @_;
76              
77             # Don't load the same plugin twice!
78             # Do this *before* calling ->new(), otherwise eval rules will be
79             # registered on a nonexistent object
80 4680         5847 foreach my $old_plugin (@{$self->{plugins}}) {
  4680         9213  
81 64044 100       108924 if (ref($old_plugin) eq $package) {
82 2887         8809 dbg("plugin: did not register $package, already registered");
83 2887         9700 return;
84             }
85             }
86              
87 1793         2637 my $ret;
88 1793 50       3138 if ($path) {
89             # bug 3717:
90             # At least Perl 5.8.0 seems to confuse $cwd internally at some point -- we
91             # need to use an absolute path here else we get a "File not found" error.
92 0         0 $path = Mail::SpamAssassin::Util::untaint_file_path(
93             File::Spec->rel2abs($path)
94             );
95              
96             # if (exists $INC{$path}) {
97             # dbg("plugin: not loading $package from $path, already loaded");
98             # return;
99             # }
100              
101 0         0 dbg("plugin: loading $package from $path");
102              
103             # use require instead of "do", so we get built-in $INC{filename}
104             # smarts
105 0         0 $ret = eval { require $path; };
  0         0  
106             }
107             else {
108 1793         7355 dbg("plugin: loading $package from \@INC");
109 1793         96782 $ret = eval qq{ require $package; };
110 1793         6236 $path = "(from \@INC)";
111             }
112              
113 1793 50       4675 if (!$ret) {
114 0 0       0 if ($silent) {
115 0 0       0 if ($@) { dbg("plugin: failed to parse tryplugin $path: $@\n"); }
  0 0       0  
116 0         0 elsif ($!) { dbg("plugin: failed to load tryplugin $path: $!\n"); }
117             }
118             else {
119 0 0       0 if ($@) { warn "plugin: failed to parse plugin $path: $@\n"; }
  0 0       0  
120 0         0 elsif ($!) { warn "plugin: failed to load plugin $path: $!\n"; }
121             }
122 0         0 return; # failure! no point in continuing here
123             }
124              
125 1793         93171 my $plugin = eval $package.q{->new ($self->{main}); };
126              
127 1793 50 33     10799 if ($@ || !$plugin) {
128 0         0 warn "plugin: failed to create instance of plugin $package: $@\n";
129             }
130              
131 1793 50       4420 if ($plugin) {
132 1793         5427 $self->{main}->{plugins}->register_plugin ($plugin);
133 1793         5125 $self->{main}->{conf}->load_plugin_succeeded ($plugin, $package, $path);
134             }
135             }
136              
137             sub register_plugin {
138 1793     1793 0 3017 my ($self, $plugin) = @_;
139 1793         2910 $plugin->{main} = $self->{main};
140 1793         2200 push (@{$self->{plugins}}, $plugin);
  1793         3806  
141             # dbg("plugin: registered $plugin");
142              
143             # invalidate cache entries for any configuration-time hooks, in case
144             # one has already been built; this plugin may implement that hook!
145 1793         3835 foreach my $subname (@CONFIG_TIME_HOOKS) {
146 1793         3656 delete $self->{cbs}->{$subname};
147             }
148             }
149              
150             ###########################################################################
151              
152             sub have_callback {
153 1644     1644 0 2857 my ($self, $subname) = @_;
154              
155             # have we set up the cache entry for this callback type?
156 1644 100       3631 if (!exists $self->{cbs}->{$subname}) {
157             # nope. run through all registered plugins and see which ones
158             # implement this type of callback. sort by priority
159              
160 1178         1600 my %subsbypri;
161 1178         1553 foreach my $plugin (@{$self->{plugins}}) {
  1178         3000  
162 27228         83149 my $methodref = $plugin->can ($subname);
163 27228 100       44605 if (defined $methodref) {
164 555   50     2836 my $pri = $plugin->{method_priority}->{$subname} || 0;
165              
166 555   100     2948 $subsbypri{$pri} ||= [];
167 555         861 push (@{$subsbypri{$pri}}, [ $plugin, $methodref ]);
  555         1609  
168              
169 555         2818 dbg("plugin: ${plugin} implements '$subname', priority $pri");
170             }
171             }
172              
173 1178         2168 my @subs;
174 1178         3641 foreach my $pri (sort { $a <=> $b } keys %subsbypri) {
  0         0  
175 452         770 push @subs, @{$subsbypri{$pri}};
  452         1152  
176             }
177              
178 1178         4900 $self->{cbs}->{$subname} = \@subs;
179             }
180              
181 1644         2346 return scalar(@{$self->{cbs}->{$subname}});
  1644         7056  
182             }
183              
184             sub callback {
185 5414     5414 0 7069 my $self = shift;
186 5414         6733 my $subname = shift;
187 5414         7184 my ($ret, $overallret);
188              
189             # have we set up the cache entry for this callback type?
190 5414 100       12257 if (!exists $self->{cbs}->{$subname}) {
191 1096 100       2487 return unless $self->have_callback($subname);
192             }
193              
194 4770         6411 foreach my $cbpair (@{$self->{cbs}->{$subname}}) {
  4770         10842  
195 2000         3458 my ($plugin, $methodref) = @$cbpair;
196              
197 2000         3026 $plugin->{_inhibit_further_callbacks} = 0;
198              
199             eval {
200 2000         6310 $ret = &$methodref ($plugin, @_);
201 2000         4213 1;
202 2000 50       2661 } or do {
203 0 0       0 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0         0  
204 0         0 warn "plugin: eval failed: $eval_stat\n";
205             };
206              
207 2000 100       3788 if (defined $ret) {
208             # dbg("plugin: ${plugin}->${methodref} => $ret");
209             # we are interested in defined but false results too
210 1905 100 100     5117 $overallret = $ret if $ret || !defined $overallret;
211             }
212              
213 2000 50       4652 if ($plugin->{_inhibit_further_callbacks}) {
214             # dbg("plugin: $plugin inhibited further callbacks");
215 0         0 last;
216             }
217             }
218              
219 4770         19474 return $overallret;
220             }
221              
222             ###########################################################################
223              
224             sub get_loaded_plugins_list {
225 0     0 0 0 my ($self) = @_;
226 0         0 return @{$self->{plugins}};
  0         0  
227             }
228              
229             ###########################################################################
230              
231             sub finish {
232 40     40 0 170 my $self = shift;
233 40         576 delete $self->{cbs};
234 40         108 foreach my $plugin (@{$self->{plugins}}) {
  40         198  
235 1119         3421 $plugin->finish();
236 1119         1505 delete $plugin->{main};
237             }
238 40         908 delete $self->{plugins};
239 40         142 delete $self->{main};
240             }
241              
242             ###########################################################################
243              
244             1;