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              
25             use Mail::SpamAssassin;
26 41     41   394 use Mail::SpamAssassin::Plugin;
  41         107  
  41         1211  
27 41     41   11670 use Mail::SpamAssassin::Util;
  41         120  
  41         1347  
28 41     41   267 use Mail::SpamAssassin::Logger;
  41         106  
  41         1728  
29 41     41   241  
  41         89  
  41         1624  
30             use strict;
31 41     41   218 use warnings;
  41         94  
  41         648  
32 41     41   201 # use bytes;
  41         85  
  41         923  
33             use re 'taint';
34 41     41   213 use File::Spec;
  41         139  
  41         1414  
35 41     41   247  
  41         94  
  41         47614  
36             our @ISA = qw();
37              
38             #Removed $VERSION per BUG 6422
39             #$VERSION = 'bogus'; # avoid CPAN.pm picking up version strings later
40              
41             # Normally, the list of active plugins that should be called for a given hook
42             # method name is compiled and cached at runtime. This means that later calls
43             # will not have to traverse the entire plugin list more than once, since the
44             # list of plugins that implement that hook is already cached.
45             #
46             # However, some hooks should not receive this treatment. One of these is
47             # parse_config, which may be compiled before all config files have been read;
48             # if a plugin is loaded from a config file after this has been compiled, it
49             # will not get callbacks.
50             #
51             # Any other such hooks that may be compiled at config-parse-time should be
52             # listed here.
53              
54             our @CONFIG_TIME_HOOKS = qw( parse_config );
55              
56             ###########################################################################
57              
58             my $class = shift;
59             my $main = shift;
60 92     92 0 246 $class = ref($class) || $class;
61 92         165 my $self = {
62 92   33     528 plugins => [ ],
63 92         741 cbs => { },
64             main => $main
65             };
66             bless ($self, $class);
67             $self;
68 92         361 }
69 92         293  
70             ###########################################################################
71              
72             my ($self, $package, $path, $silent) = @_;
73              
74             # Strict name checking
75 5232     5232 0 11748 if ($package !~ /^(?:\w+::){0,10}\w+$/) {
76             warn "plugin: illegal plugin name, not loading: $package\n";
77             return;
78 5232 50       20856 }
79 0         0 $package = Mail::SpamAssassin::Util::untaint_var($package);
80 0         0  
81             # Don't load the same plugin twice!
82 5232         14621 # Do this *before* calling ->new(), otherwise eval rules will be
83             # registered on a nonexistent object
84             foreach my $old_plugin (@{$self->{plugins}}) {
85             if (ref($old_plugin) eq $package) {
86             dbg("plugin: did not register $package, already registered");
87 5232         7154 return;
  5232         10738  
88 82698 100       144826 }
89 2832         9038 }
90 2832         11625  
91             my $ret;
92             if ($path) {
93             if ($path !~ /^\S+\.pm/i) {
94 2400         3388 warn "plugin: illegal plugin filename, not loading: $path";
95 2400 50       4012 return;
96 0 0       0 }
97 0         0  
98 0         0 $path = $self->{main}->{conf}->{parser}->fix_path_relative_to_current_file($path);
99              
100             # bug 3717:
101 0         0 # At least Perl 5.8.0 seems to confuse $cwd internally at some point -- we
102             # need to use an absolute path here else we get a "File not found" error.
103             $path = Mail::SpamAssassin::Util::untaint_file_path(
104             File::Spec->rel2abs($path)
105             );
106 0         0  
107             # if (exists $INC{$path}) {
108             # dbg("plugin: not loading $package from $path, already loaded");
109             # return;
110             # }
111              
112             dbg("plugin: loading $package from $path");
113              
114             # use require instead of "do", so we get built-in $INC{filename}
115 0         0 # smarts
116             $ret = eval { require $path; };
117             }
118             else {
119 0         0 dbg("plugin: loading $package from \@INC");
  0         0  
120             $ret = eval qq{ require $package; };
121             $path = "(from \@INC)";
122 2400         9650 }
123 2400         128483  
124 2400         8392 if (!$ret) {
125             if ($silent) {
126             if ($@) { dbg("plugin: failed to parse tryplugin $path: $@\n"); }
127 2400 50       6208 elsif ($!) { dbg("plugin: failed to load tryplugin $path: $!\n"); }
128 0 0       0 }
129 0 0       0 else {
  0 0       0  
130 0         0 if ($@) { warn "plugin: failed to parse plugin $path: $@\n"; }
131             elsif ($!) { warn "plugin: failed to load plugin $path: $!\n"; }
132             }
133 0 0       0 return; # failure! no point in continuing here
  0 0       0  
134 0         0 }
135              
136 0         0 my $plugin = eval $package.q{->new ($self->{main}); };
137              
138             if ($@ || !$plugin) {
139 2400         116134 warn "plugin: failed to create instance of plugin $package: $@\n";
140             }
141 2400 50 33     13459  
142 0         0 if ($plugin) {
143             $self->{main}->{plugins}->register_plugin ($plugin);
144             $self->{main}->{conf}->load_plugin_succeeded ($plugin, $package, $path);
145 2400 50       5663 }
146 2400         6844 }
147 2400         6822  
148             my ($self, $plugin) = @_;
149             $plugin->{main} = $self->{main};
150             push (@{$self->{plugins}}, $plugin);
151             # dbg("plugin: registered $plugin");
152 2400     2400 0 3938  
153 2400         3790 # invalidate cache entries for any configuration-time hooks, in case
154 2400         2976 # one has already been built; this plugin may implement that hook!
  2400         4806  
155             foreach my $subname (@CONFIG_TIME_HOOKS) {
156             delete $self->{cbs}->{$subname};
157             }
158             }
159 2400         4706  
160 2400         4766 ###########################################################################
161              
162             my ($self, $subname) = @_;
163              
164             # have we set up the cache entry for this callback type?
165             if (!exists $self->{cbs}->{$subname}) {
166             # nope. run through all registered plugins and see which ones
167 1823     1823 0 3104 # implement this type of callback. sort by priority
168              
169             my %subsbypri;
170 1823 100       3687 foreach my $plugin (@{$self->{plugins}}) {
171             my $methodref = $plugin->can ($subname);
172             if (defined $methodref) {
173             my $pri = $plugin->{method_priority}->{$subname} || 0;
174 1250         1675  
175 1250         1592 $subsbypri{$pri} ||= [];
  1250         3149  
176 36720         99813 push (@{$subsbypri{$pri}}, [ $plugin, $methodref ]);
177 36720 100       60549  
178 1023   50     4308 dbg("plugin: ${plugin} implements '$subname', priority $pri");
179             }
180 1023   100     4374 }
181 1023         1343  
  1023         2585  
182             my @subs;
183 1023         4772 foreach my $pri (sort { $a <=> $b } keys %subsbypri) {
184             push @subs, @{$subsbypri{$pri}};
185             }
186              
187 1250         1900 $self->{cbs}->{$subname} = \@subs;
188 1250         3682 }
  0         0  
189 636         1135  
  636         1630  
190             return scalar(@{$self->{cbs}->{$subname}});
191             }
192 1250         5058  
193             my $self = shift;
194             my $subname = shift;
195 1823         2602 my ($ret, $overallret);
  1823         7056  
196              
197             # have we set up the cache entry for this callback type?
198             if (!exists $self->{cbs}->{$subname}) {
199 5581     5581 0 6920 return unless $self->have_callback($subname);
200 5581         6652 }
201 5581         7043  
202             foreach my $cbpair (@{$self->{cbs}->{$subname}}) {
203             my ($plugin, $methodref) = @$cbpair;
204 5581 100       12557  
205 1168 100       2674 $plugin->{_inhibit_further_callbacks} = 0;
206              
207             eval {
208 5049         6285 $ret = &$methodref ($plugin, @_);
  5049         10945  
209 4873         7944 1;
210             } or do {
211 4873         6889 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
212             warn "plugin: eval failed: $eval_stat\n";
213             };
214 4873         13150  
215 4873         9744 if (defined $ret) {
216 4873 50       5940 # dbg("plugin: ${plugin}->${methodref} => $ret");
217 0 0       0 # we are interested in defined but false results too
  0         0  
218 0         0 $overallret = $ret if $ret || !defined $overallret;
219             }
220              
221 4873 100       8658 if ($plugin->{_inhibit_further_callbacks}) {
222             # dbg("plugin: $plugin inhibited further callbacks");
223             last;
224 4444 100 100     13432 }
225             }
226              
227 4873 50       11395 return $overallret;
228             }
229 0         0  
230             ###########################################################################
231              
232             my ($self) = @_;
233 5049         20409 return @{$self->{plugins}};
234             }
235              
236             ###########################################################################
237              
238             my $self = shift;
239 0     0 0 0 delete $self->{cbs};
240 0         0 foreach my $plugin (@{$self->{plugins}}) {
  0         0  
241             $plugin->finish();
242             delete $plugin->{main};
243             }
244             delete $self->{plugins};
245             delete $self->{main};
246 52     52 0 131 }
247 52         663  
248 52         105 ###########################################################################
  52         258  
249 1520         4673  
250 1520         1955 1;