our
@ISA
=
qw(Mail::SpamAssassin::Plugin)
;
our
@TEMPORARY_METHODS
;
my
$RULENAME_RE
= RULENAME_RE;
sub
new {
my
$class
=
shift
;
my
$samain
=
shift
;
$class
=
ref
(
$class
) ||
$class
;
my
$self
=
$class
->SUPER::new(
$samain
);
bless
(
$self
,
$class
);
$self
->set_config(
$samain
->{conf});
return
$self
;
}
sub
set_config {
my
(
$self
,
$conf
) =
@_
;
my
@cmds
;
my
$pluginobj
=
$self
;
push
(
@cmds
, {
setting
=>
'mimeheader'
,
is_priv
=> 1,
code
=>
sub
{
my
(
$self
,
$key
,
$value
,
$line
) =
@_
;
local
($1,$2,$3);
if
(
$value
!~ s/^(${RULENAME_RE})\s+//) {
return
$Mail::SpamAssassin::Conf::INVALID_VALUE
;
}
my
$rulename
= untaint_var($1);
if
(
$value
eq
''
) {
return
$Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE
;
}
if
(
$value
!~ /^([^:\s]+(?:\:(?:raw)?)?)\s*([=!]~)\s*(.+)$/) {
return
$Mail::SpamAssassin::Conf::INVALID_VALUE
;
}
my
$hdrname
= $1;
my
$negated
= $2 eq
'!~'
? 1 : 0;
my
$pattern
= $3;
$hdrname
=~ s/:$//;
my
$if_unset
=
''
;
if
(
$pattern
=~ s/\s+\[
if
-unset:\s+(.+)\]$//) {
$if_unset
= $1;
}
my
(
$rec
,
$err
) = compile_regexp(
$pattern
, 1);
if
(!
$rec
) {
info(
"mimeheader: invalid regexp for $rulename '$pattern': $err"
);
return
$Mail::SpamAssassin::Conf::INVALID_VALUE
;
}
$self
->{mimeheader_tests}->{
$rulename
} = {
hdr
=>
$hdrname
,
negated
=>
$negated
,
if_unset
=>
$if_unset
,
pattern
=>
$rec
};
my
$evalfn
=
"_mimeheader_eval_$rulename"
;
return
if
(
defined
&{
'Mail::SpamAssassin::Plugin::MIMEHeader::'
.
$evalfn
});
$self
->{parser}->add_test(
$rulename
,
$evalfn
.
"()"
,
$Mail::SpamAssassin::Conf::TYPE_BODY_EVALS
);
$self
->{parser}->parse_captures(
$rulename
,
$rec
);
my
$evalcode
= '
sub
Mail::SpamAssassin::Plugin::MIMEHeader::
'.$evalfn.'
{
$_
[0]->eval_hook_called(
$_
[1],
q{'.$rulename.'}
);
}
';
eval
$evalcode
.
'; 1'
or
do
{
my
$eval_stat
= $@ ne
''
? $@ :
"errno=$!"
;
chomp
$eval_stat
;
warn
"mimeheader: plugin error: $eval_stat\n"
;
return
$Mail::SpamAssassin::Conf::INVALID_VALUE
;
};
$pluginobj
->register_eval_rule(
$evalfn
);
push
@TEMPORARY_METHODS
,
"Mail::SpamAssassin::Plugin::MIMEHeader::${evalfn}"
;
}
});
$conf
->{parser}->register_commands(\
@cmds
);
}
sub
eval_hook_called {
my
(
$pobj
,
$pms
,
$rulename
) =
@_
;
my
$conf
=
$pms
->{conf};
my
$rule
=
$conf
->{mimeheader_tests}->{
$rulename
};
my
$hdr
=
$rule
->{hdr};
my
$negated
=
$rule
->{negated};
my
$pattern
=
$rule
->{pattern};
my
$tflags
=
$conf
->{tflags}->{
$rulename
}||
''
;
my
$getraw
= 0;
if
(
$hdr
=~ s/:raw$//) {
$getraw
= 1;
}
my
$range_min
= 0;
my
$range_max
= 1000;
if
(
$tflags
=~ /(?:^|\s)range=(\d+)?(-)?(\d+)?(?:\s|$)/) {
if
(
defined
$1 &&
defined
$2 &&
defined
$3) {
$range_min
= $1;
$range_max
= $3;
}
elsif
(
defined
$1 &&
defined
$2) {
$range_min
= $1;
}
elsif
(
defined
$2 &&
defined
$3) {
$range_max
= $3;
}
elsif
(
defined
$1) {
$range_min
=
$range_max
= $1;
}
}
my
$multiple
=
$tflags
=~ /\bmultiple\b/;
my
$concat
=
$tflags
=~ /\bconcat\b/;
my
$maxhits
=
$tflags
=~ /\bmaxhits=(\d+)\b/ ? $1 :
$multiple
? 1000 : 1;
my
$cval
=
''
;
my
$idx
= 0;
foreach
my
$p
(
$pms
->{msg}->find_parts(
qr/./
)) {
$idx
++;
last
if
$idx
>
$range_max
;
next
if
$idx
<
$range_min
;
my
$val
;
if
(
$hdr
eq
'ALL'
) {
$val
=
$p
->get_all_headers(
$getraw
, 0);
}
elsif
(
$getraw
) {
$val
=
$p
->raw_header(
$hdr
);
}
else
{
$val
=
$p
->get_header(
$hdr
);
}
$val
=
$rule
->{if_unset}
if
!
defined
$val
;
if
(
$concat
) {
$val
.=
"\n"
unless
$val
=~ /\n$/;
$cval
.=
$val
;
next
;
}
if
(_check(
$pms
,
$rulename
,
$val
,
$pattern
,
$negated
,
$maxhits
,
"part $idx"
)) {
return
0;
}
}
if
(
$concat
) {
if
(_check(
$pms
,
$rulename
,
$cval
,
$pattern
,
$negated
,
$maxhits
,
'concat'
)) {
return
0;
}
}
if
(
$negated
) {
dbg(
"mimeheader: ran rule $rulename ======> got hit: \"<negative match>\""
);
return
1;
}
return
0;
}
sub
_check {
my
(
$pms
,
$rulename
,
$value
,
$pattern
,
$negated
,
$maxhits
,
$desc
) =
@_
;
my
$hits
= 0;
my
%captures
;
while
(
$value
=~ /
$pattern
/gp) {
last
if
$negated
;
if
(%-) {
foreach
my
$cname
(
keys
%-) {
push
@{
$captures
{
$cname
}},
grep
{
$_
ne
""
} @{$-{
$cname
}};
}
}
my
$match
=
defined
${^MATCH} ? ${^MATCH} :
"<negative match>"
;
$pms
->got_hit(
$rulename
,
''
,
ruletype
=>
'eval'
);
dbg(
"mimeheader: ran rule $rulename ======> got hit: \"$match\" ($desc)"
);
last
if
++
$hits
>=
$maxhits
;
}
$pms
->set_captures(\
%captures
)
if
%captures
;
return
$hits
;
}
sub
finish_tests {
my
(
$self
,
$params
) =
@_
;
foreach
my
$method
(
@TEMPORARY_METHODS
) {
undef
&{
$method
};
}
@TEMPORARY_METHODS
= ();
}
sub
has_all_header { 1 }
sub
has_tflags_range { 1 }
sub
has_tflags_concat { 1 }
sub
has_tflags_multiple { 1 }
sub
has_capture_rules { 1 }
1;