File Coverage

blib/lib/Mail/SpamAssassin/Plugin/MIMEHeader.pm
Criterion Covered Total %
statement 39 93 41.9
branch 0 30 0.0
condition 1 3 33.3
subroutine 11 13 84.6
pod 2 4 50.0
total 53 143 37.0


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             MIMEHeader - perform regexp tests against MIME headers
21              
22             =head1 SYNOPSIS
23              
24             loadplugin Mail::SpamAssassin::Plugin::MIMEHeader
25             mimeheader NAME_OF_RULE Content-Id =~ /foo/
26              
27             =head1 DESCRIPTION
28              
29             This plugin allows regexp rules to be written against MIME headers in the
30             message.
31              
32             =head1 RULE DEFINITIONS AND PRIVILEGED SETTINGS
33              
34             =over 4
35              
36             =item mimeheader NAME_OF_RULE Header-Name =~ /pattern/modifiers
37              
38             Specify a rule. C<NAME_OF_RULE> is the name of the rule to be used,
39             C<Header-Name> is the name of the MIME header to check, and
40             C</pattern/modifiers> is the Perl regular expression to match against this.
41              
42             Note that in a message of multiple parts, each header will be checked
43             against the pattern separately. In other words, if multiple parts
44             have a 'Content-Type' header, each header's value will be tested
45             individually as a separate string.
46              
47             Header names are considered case-insensitive.
48              
49             The header values are normally cleaned up a little; for example, whitespace
50             around the newline character in "folded" headers will be replaced with a single
51             space. Append C<:raw> to the header name to retrieve the raw, undecoded value,
52             including pristine whitespace, instead.
53              
54             =back
55              
56             =cut
57              
58              
59             use strict;
60 22     22   148 use warnings;
  22         47  
  22         624  
61 22     22   109 # use bytes;
  22         43  
  22         656  
62             use re 'taint';
63 22     22   148  
  22         46  
  22         658  
64             use Mail::SpamAssassin::Plugin;
65 22     22   147 use Mail::SpamAssassin::Conf;
  22         59  
  22         594  
66 22     22   159 use Mail::SpamAssassin::Logger;
  22         56  
  22         582  
67 22     22   127 use Mail::SpamAssassin::Util qw(untaint_var compile_regexp);
  22         49  
  22         1373  
68 22     22   165 use Mail::SpamAssassin::Constants qw(:sa);
  22         47  
  22         1494  
69 22     22   157  
  22         72  
  22         22762  
70             our @ISA = qw(Mail::SpamAssassin::Plugin);
71              
72             our @TEMPORARY_METHODS;
73              
74             my $RULENAME_RE = RULENAME_RE;
75              
76             # ---------------------------------------------------------------------------
77              
78             # constructor
79             my $class = shift;
80             my $samain = shift;
81 63     63 1 190  
82 63         125 # some boilerplate...
83             $class = ref($class) || $class;
84             my $self = $class->SUPER::new($samain);
85 63   33     394 bless ($self, $class);
86 63         322  
87 63         155 $self->set_config($samain->{conf});
88              
89 63         301 return $self;
90             }
91 63         543  
92             # ---------------------------------------------------------------------------
93              
94             my($self, $conf) = @_;
95             my @cmds;
96              
97 63     63 0 160 my $pluginobj = $self; # allow use inside the closure below
98 63         130  
99             push (@cmds, {
100 63         115 setting => 'mimeheader',
101             is_priv => 1,
102             code => sub {
103             my ($self, $key, $value, $line) = @_;
104             local ($1,$2,$3);
105             if ($value !~ s/^(${RULENAME_RE})\s+//) {
106 0     0   0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
107 0         0 }
108 0 0       0 my $rulename = untaint_var($1);
109 0         0 if ($value eq '') {
110             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
111 0         0 }
112 0 0       0 # Take :raw to hdrname!
113 0         0 if ($value !~ /^([^:\s]+(?:\:(?:raw)?)?)\s*([=!]~)\s*(.+)$/) {
114             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
115             }
116 0 0       0 my $hdrname = $1;
117 0         0 my $negated = $2 eq '!~' ? 1 : 0;
118             my $pattern = $3;
119 0         0 $hdrname =~ s/:$//;
120 0 0       0 my $if_unset = '';
121 0         0 if ($pattern =~ s/\s+\[if-unset:\s+(.+)\]$//) {
122 0         0 $if_unset = $1;
123 0         0 }
124 0 0       0 my ($rec, $err) = compile_regexp($pattern, 1);
125 0         0 if (!$rec) {
126             info("mimeheader: invalid regexp for $rulename '$pattern': $err");
127 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
128 0 0       0 }
129 0         0  
130 0         0 $self->{mimeheader_tests}->{$rulename} = {
131             hdr => $hdrname,
132             negated => $negated,
133 0         0 if_unset => $if_unset,
134             pattern => $rec
135             };
136              
137             # now here's a hack; generate a fake eval rule function to
138             # call this rule's _real_ code!
139             # TODO: we should have a more elegant way for new rule types to
140             # be defined
141             my $evalfn = "_mimeheader_eval_$rulename";
142              
143             # don't redefine the subroutine if it already exists!
144 0         0 # this causes lots of annoying warnings and such during things like
145             # "make test".
146             return if (defined &{'Mail::SpamAssassin::Plugin::MIMEHeader::'.$evalfn});
147              
148             $self->{parser}->add_test($rulename, $evalfn."()",
149 0 0       0 $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
  0         0  
150              
151 0         0 # evalfn/rulename safe, sanitized by $RULENAME_RE
152             my $evalcode = '
153             sub Mail::SpamAssassin::Plugin::MIMEHeader::'.$evalfn.' {
154             $_[0]->eval_hook_called($_[1], q{'.$rulename.'});
155 0         0 }
156             ';
157              
158             eval
159             $evalcode . '; 1'
160             or do {
161             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
162             warn "mimeheader: plugin error: $eval_stat\n";
163 0 0       0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
164 0 0       0 };
  0         0  
165 0         0  
166 0         0 $pluginobj->register_eval_rule($evalfn);
167              
168             push @TEMPORARY_METHODS, "Mail::SpamAssassin::Plugin::MIMEHeader::${evalfn}";
169 0         0 }
170             });
171 0         0  
172             $conf->{parser}->register_commands(\@cmds);
173 63         716 }
174              
175 63         310 # ---------------------------------------------------------------------------
176              
177             my ($pobj, $scanner, $rulename) = @_;
178              
179             my $rule = $scanner->{conf}->{mimeheader_tests}->{$rulename};
180             my $hdr = $rule->{hdr};
181 0     0 0 0 my $negated = $rule->{negated};
182             my $if_unset = $rule->{if_unset};
183 0         0 my $pattern = $rule->{pattern};
184 0         0  
185 0         0  
186 0         0 my $getraw;
187 0         0 if ($hdr =~ s/:raw$//) {
188             $getraw = 1;
189             } else {
190 0         0 $getraw = 0;
191 0 0       0 }
192 0         0  
193             foreach my $p ($scanner->{msg}->find_parts(qr/./)) {
194 0         0 my $val;
195             if ($getraw) {
196             $val = $p->raw_header($hdr);
197 0         0 } else {
198 0         0 $val = $p->get_header($hdr);
199 0 0       0 }
200 0         0 $val = $if_unset if !defined $val;
201              
202 0         0 if ($val =~ $pattern) {
203             return ($negated ? 0 : 1);
204 0 0       0 }
205             }
206 0 0       0  
207 0 0       0 return ($negated ? 1 : 0);
208             }
209              
210             # ---------------------------------------------------------------------------
211 0 0       0  
212             my ($self, $params) = @_;
213              
214             foreach my $method (@TEMPORARY_METHODS) {
215             undef &{$method};
216             }
217 40     40 1 106 @TEMPORARY_METHODS = (); # clear for next time
218             }
219 40         122  
220 0         0 # ---------------------------------------------------------------------------
  0         0  
221              
222 40         153 1;