File Coverage

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