File Coverage

blib/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm
Criterion Covered Total %
statement 36 114 31.5
branch 0 42 0.0
condition 1 27 3.7
subroutine 7 19 36.8
pod 1 13 7.6
total 45 215 20.9


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             package Mail::SpamAssassin::Plugin::HTMLEval;
19              
20 21     21   162 use strict;
  21         47  
  21         704  
21 21     21   120 use warnings;
  21         48  
  21         740  
22             # use bytes;
23 21     21   144 use re 'taint';
  21         49  
  21         731  
24              
25 21     21   148 use Mail::SpamAssassin::Plugin;
  21         59  
  21         596  
26 21     21   7304 use Mail::SpamAssassin::Locales;
  21         62  
  21         711  
27 21     21   146 use Mail::SpamAssassin::Util qw(untaint_var compile_regexp);
  21         48  
  21         28784  
28              
29             our @ISA = qw(Mail::SpamAssassin::Plugin);
30              
31             # constructor: register the eval rule
32             sub new {
33 62     62 1 263 my $class = shift;
34 62         166 my $mailsaobject = shift;
35              
36             # some boilerplate...
37 62   33     481 $class = ref($class) || $class;
38 62         367 my $self = $class->SUPER::new($mailsaobject);
39 62         190 bless ($self, $class);
40              
41             # the important bit!
42 62         319 $self->register_eval_rule("html_tag_balance");
43 62         236 $self->register_eval_rule("html_image_only");
44 62         228 $self->register_eval_rule("html_image_ratio");
45 62         235 $self->register_eval_rule("html_charset_faraway");
46 62         247 $self->register_eval_rule("html_tag_exists");
47 62         246 $self->register_eval_rule("html_test");
48 62         248 $self->register_eval_rule("html_eval");
49 62         205 $self->register_eval_rule("html_text_match");
50 62         229 $self->register_eval_rule("html_title_subject_ratio");
51 62         240 $self->register_eval_rule("html_text_not_match");
52 62         240 $self->register_eval_rule("html_range");
53 62         247 $self->register_eval_rule("check_iframe_src");
54              
55 62         582 return $self;
56             }
57              
58             sub html_tag_balance {
59 0     0 0   my ($self, $pms, undef, $rawtag, $rawexpr) = @_;
60              
61 0 0         return 0 if $rawtag !~ /^([a-zA-Z0-9]+)$/;
62 0           my $tag = $1;
63              
64 0 0         return 0 unless exists $pms->{html}{inside}{$tag};
65              
66 0 0         return 0 if $rawexpr !~ /^([\<\>\=\!\-\+ 0-9]+)$/;
67 0           my $expr = untaint_var($1);
68              
69 0           $pms->{html}{inside}{$tag} =~ /^([\<\>\=\!\-\+ 0-9]+)$/;
70 0           my $val = untaint_var($1);
71              
72 0           return eval "\$val $expr";
73             }
74              
75             sub html_image_only {
76 0     0 0   my ($self, $pms, undef, $min, $max) = @_;
77              
78             return (exists $pms->{html}{inside}{img} &&
79             exists $pms->{html}{length} &&
80             $pms->{html}{length} > $min &&
81 0   0       $pms->{html}{length} <= $max);
82             }
83              
84             sub html_image_ratio {
85 0     0 0   my ($self, $pms, undef, $min, $max) = @_;
86              
87             return 0 unless (exists $pms->{html}{non_space_len} &&
88             exists $pms->{html}{image_area} &&
89 0 0 0       $pms->{html}{image_area} > 0);
      0        
90 0           my $ratio = $pms->{html}{non_space_len} / $pms->{html}{image_area};
91 0   0       return ($ratio > $min && $ratio <= $max);
92             }
93              
94             sub html_charset_faraway {
95 0     0 0   my ($self, $pms) = @_;
96              
97 0 0         return 0 unless exists $pms->{html}{charsets};
98              
99 0           my @locales = Mail::SpamAssassin::Util::get_my_locales($pms->{conf}->{ok_locales});
100 0 0         return 0 if grep { $_ eq "all" } @locales;
  0            
101              
102 0           my $okay = 0;
103 0           my $bad = 0;
104 0           for my $c (split(' ', $pms->{html}{charsets})) {
105 0 0         if (Mail::SpamAssassin::Locales::is_charset_ok_for_locales($c, @locales)) {
106 0           $okay++;
107             }
108             else {
109 0           $bad++;
110             }
111             }
112 0   0       return ($bad && ($bad >= $okay));
113             }
114              
115             sub html_tag_exists {
116 0     0 0   my ($self, $pms, undef, $tag) = @_;
117 0           return exists $pms->{html}{inside}{$tag};
118             }
119              
120             sub html_test {
121 0     0 0   my ($self, $pms, undef, $test) = @_;
122 0           return $pms->{html}{$test};
123             }
124              
125             sub html_eval {
126 0     0 0   my ($self, $pms, undef, $test, $rawexpr) = @_;
127              
128 0 0         return 0 if $rawexpr !~ /^([\<\>\=\!\-\+ 0-9]+)$/;
129 0           my $expr = untaint_var($1);
130              
131             # workaround bug 3320: weird perl bug where additional, very explicit
132             # untainting into a new var is required.
133 0           my $tainted = $pms->{html}{$test};
134 0 0         return 0 unless defined($tainted);
135 0           my $val = $tainted;
136              
137             # just use the value in $val, don't copy it needlessly
138 0           return eval "\$val $expr";
139             }
140              
141             sub html_text_match {
142 0     0 0   my ($self, $pms, undef, $text, $regexp) = @_;
143 0           my ($rec, $err) = compile_regexp($regexp, 0);
144 0 0         if (!$rec) {
145 0           warn "htmleval: html_text_match invalid regexp '$regexp': $err";
146 0           return 0;
147             }
148 0           foreach my $string (@{$pms->{html}{$text}}) {
  0            
149 0 0         next unless defined $string;
150 0 0         if ($string =~ $rec) {
151 0           return 1;
152             }
153             }
154 0           return 0;
155             }
156              
157             sub html_title_subject_ratio {
158 0     0 0   my ($self, $pms, undef, $ratio) = @_;
159              
160 0           my $subject = $pms->get('Subject');
161 0 0         if ($subject eq '') {
162 0           return 0;
163             }
164 0           my $max = 0;
165 0           for my $string (@{ $pms->{html}{title} }) {
  0            
166 0 0         if ($string) {
167 0           my $ratio = length($string) / length($subject);
168 0 0         $max = $ratio if $ratio > $max;
169             }
170             }
171 0           return $max > $ratio;
172             }
173              
174             sub html_text_not_match {
175 0     0 0   my ($self, $pms, undef, $text, $regexp) = @_;
176 0           for my $string (@{ $pms->{html}{$text} }) {
  0            
177 0 0 0       if (defined $string && $string !~ /${regexp}/) {
178 0           return 1;
179             }
180             }
181 0           return 0;
182             }
183              
184             sub html_range {
185 0     0 0   my ($self, $pms, undef, $test, $min, $max) = @_;
186              
187 0 0         return 0 unless exists $pms->{html}{$test};
188              
189 0           $test = $pms->{html}{$test};
190              
191             # not all perls understand what "inf" means, so we need to do
192             # non-numeric tests! urg!
193 0 0 0       if (!defined $max || $max eq "inf") {
    0          
194 0 0         return ($test eq "inf") ? 1 : ($test > $min);
195             }
196             elsif ($test eq "inf") {
197             # $max < inf, so $test == inf means $test > $max
198 0           return 0;
199             }
200             else {
201             # if we get here everything should be a number
202 0   0       return ($test > $min && $test <= $max);
203             }
204             }
205              
206             sub check_iframe_src {
207 0     0 0   my ($self, $pms) = @_;
208              
209 0           foreach my $v ( values %{$pms->{html}->{uri_detail}} ) {
  0            
210 0 0         return 1 if $v->{types}->{iframe};
211             }
212              
213 0           return 0;
214             }
215              
216             1;