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