File Coverage

blib/lib/Mail/SpamAssassin/PluginHandler.pm
Criterion Covered Total %
statement 98 125 78.4
branch 21 42 50.0
condition 8 13 61.5
subroutine 14 15 93.3
pod 0 7 0.0
total 141 202 69.8


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   358 use Mail::SpamAssassin;
  40         126  
  40         1519  
27 40     40   13546 use Mail::SpamAssassin::Plugin;
  40         160  
  40         1642  
28 40     40   321 use Mail::SpamAssassin::Util;
  40         114  
  40         1927  
29 40     40   288 use Mail::SpamAssassin::Logger;
  40         100  
  40         1934  
30              
31 40     40   256 use strict;
  40         100  
  40         830  
32 40     40   229 use warnings;
  40         100  
  40         1124  
33             # use bytes;
34 40     40   240 use re 'taint';
  40         111  
  40         1600  
35 40     40   301 use File::Spec;
  40         134  
  40         49895  
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 91     91 0 261 my $class = shift;
61 91         207 my $main = shift;
62 91   33     545 $class = ref($class) || $class;
63 91         812 my $self = {
64             plugins => [ ],
65             cbs => { },
66             main => $main
67             };
68 91         356 bless ($self, $class);
69 91         382 $self;
70             }
71              
72             ###########################################################################
73              
74             sub load_plugin {
75 5148     5148 0 13590 my ($self, $package, $path, $silent) = @_;
76              
77             # Strict name checking
78 5148 50       23880 if ($package !~ /^(?:\w+::){0,10}\w+$/) {
79 0         0 warn "plugin: illegal plugin name, not loading: $package\n";
80 0         0 return;
81             }
82 5148         14785 $package = Mail::SpamAssassin::Util::untaint_var($package);
83              
84             # Don't load the same plugin twice!
85             # Do this *before* calling ->new(), otherwise eval rules will be
86             # registered on a nonexistent object
87 5148         8747 foreach my $old_plugin (@{$self->{plugins}}) {
  5148         12437  
88 81358 100       170213 if (ref($old_plugin) eq $package) {
89 2786         10269 dbg("plugin: did not register $package, already registered");
90 2786         14114 return;
91             }
92             }
93              
94 2362         3836 my $ret;
95 2362 50       4531 if ($path) {
96 0 0       0 if ($path !~ /^\S+\.pm/i) {
97 0         0 warn "plugin: illegal plugin filename, not loading: $path";
98 0         0 return;
99             }
100              
101 0         0 $path = $self->{main}->{conf}->{parser}->fix_path_relative_to_current_file($path);
102              
103             # bug 3717:
104             # At least Perl 5.8.0 seems to confuse $cwd internally at some point -- we
105             # need to use an absolute path here else we get a "File not found" error.
106 0         0 $path = Mail::SpamAssassin::Util::untaint_file_path(
107             File::Spec->rel2abs($path)
108             );
109              
110             # if (exists $INC{$path}) {
111             # dbg("plugin: not loading $package from $path, already loaded");
112             # return;
113             # }
114              
115 0         0 dbg("plugin: loading $package from $path");
116              
117             # use require instead of "do", so we get built-in $INC{filename}
118             # smarts
119 0         0 $ret = eval { require $path; };
  0         0  
120             }
121             else {
122 2362         10420 dbg("plugin: loading $package from \@INC");
123 2362         149725 $ret = eval qq{ require $package; };
124 2362         9763 $path = "(from \@INC)";
125             }
126              
127 2362 50       6632 if (!$ret) {
128 0 0       0 if ($silent) {
129 0 0       0 if ($@) { dbg("plugin: failed to parse tryplugin $path: $@\n"); }
  0 0       0  
130 0         0 elsif ($!) { dbg("plugin: failed to load tryplugin $path: $!\n"); }
131             }
132             else {
133 0 0       0 if ($@) { warn "plugin: failed to parse plugin $path: $@\n"; }
  0 0       0  
134 0         0 elsif ($!) { warn "plugin: failed to load plugin $path: $!\n"; }
135             }
136 0         0 return; # failure! no point in continuing here
137             }
138              
139 2362         129012 my $plugin = eval $package.q{->new ($self->{main}); };
140              
141 2362 50 33     16044 if ($@ || !$plugin) {
142 0         0 warn "plugin: failed to create instance of plugin $package: $@\n";
143             }
144              
145 2362 50       6779 if ($plugin) {
146 2362         8015 $self->{main}->{plugins}->register_plugin ($plugin);
147 2362         7131 $self->{main}->{conf}->load_plugin_succeeded ($plugin, $package, $path);
148             }
149             }
150              
151             sub register_plugin {
152 2362     2362 0 4605 my ($self, $plugin) = @_;
153 2362         4141 $plugin->{main} = $self->{main};
154 2362         3121 push (@{$self->{plugins}}, $plugin);
  2362         5502  
155             # dbg("plugin: registered $plugin");
156              
157             # invalidate cache entries for any configuration-time hooks, in case
158             # one has already been built; this plugin may implement that hook!
159 2362         5362 foreach my $subname (@CONFIG_TIME_HOOKS) {
160 2362         6008 delete $self->{cbs}->{$subname};
161             }
162             }
163              
164             ###########################################################################
165              
166             sub have_callback {
167 1818     1818 0 3631 my ($self, $subname) = @_;
168              
169             # have we set up the cache entry for this callback type?
170 1818 100       4023 if (!exists $self->{cbs}->{$subname}) {
171             # nope. run through all registered plugins and see which ones
172             # implement this type of callback. sort by priority
173              
174 1245         1871 my %subsbypri;
175 1245         1831 foreach my $plugin (@{$self->{plugins}}) {
  1245         3321  
176 36530         113243 my $methodref = $plugin->can ($subname);
177 36530 100       71251 if (defined $methodref) {
178 1016   50     5129 my $pri = $plugin->{method_priority}->{$subname} || 0;
179              
180 1016   100     4478 $subsbypri{$pri} ||= [];
181 1016         1556 push (@{$subsbypri{$pri}}, [ $plugin, $methodref ]);
  1016         2941  
182              
183 1016         4875 dbg("plugin: ${plugin} implements '$subname', priority $pri");
184             }
185             }
186              
187 1245         2136 my @subs;
188 1245         4000 foreach my $pri (sort { $a <=> $b } keys %subsbypri) {
  0         0  
189 632         1247 push @subs, @{$subsbypri{$pri}};
  632         1806  
190             }
191              
192 1245         5510 $self->{cbs}->{$subname} = \@subs;
193             }
194              
195 1818         2716 return scalar(@{$self->{cbs}->{$subname}});
  1818         7849  
196             }
197              
198             sub callback {
199 5576     5576 0 8538 my $self = shift;
200 5576         8176 my $subname = shift;
201 5576         8750 my ($ret, $overallret);
202              
203             # have we set up the cache entry for this callback type?
204 5576 100       15572 if (!exists $self->{cbs}->{$subname}) {
205 1163 100       3152 return unless $self->have_callback($subname);
206             }
207              
208 5045         7193 foreach my $cbpair (@{$self->{cbs}->{$subname}}) {
  5045         13526  
209 4866         10486 my ($plugin, $methodref) = @$cbpair;
210              
211 4866         8245 $plugin->{_inhibit_further_callbacks} = 0;
212              
213             eval {
214 4866         16331 $ret = &$methodref ($plugin, @_);
215 4866         11694 1;
216 4866 50       7166 } or do {
217 0 0       0 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0         0  
218 0         0 warn "plugin: eval failed: $eval_stat\n";
219             };
220              
221 4866 100       10440 if (defined $ret) {
222             # dbg("plugin: ${plugin}->${methodref} => $ret");
223             # we are interested in defined but false results too
224 4437 100 100     14863 $overallret = $ret if $ret || !defined $overallret;
225             }
226              
227 4866 50       14123 if ($plugin->{_inhibit_further_callbacks}) {
228             # dbg("plugin: $plugin inhibited further callbacks");
229 0         0 last;
230             }
231             }
232              
233 5045         23755 return $overallret;
234             }
235              
236             ###########################################################################
237              
238             sub get_loaded_plugins_list {
239 0     0 0 0 my ($self) = @_;
240 0         0 return @{$self->{plugins}};
  0         0  
241             }
242              
243             ###########################################################################
244              
245             sub finish {
246 52     52 0 124 my $self = shift;
247 52         819 delete $self->{cbs};
248 52         153 foreach my $plugin (@{$self->{plugins}}) {
  52         253  
249 1520         5276 $plugin->finish();
250 1520         2363 delete $plugin->{main};
251             }
252 52         1043 delete $self->{plugins};
253 52         178 delete $self->{main};
254             }
255              
256             ###########################################################################
257              
258             1;