patch-src__4.0.0-multi-casesensitive-headers-check.patch by Victor Ustugov diff -urN ../Mail-SpamAssassin-4.0.0.orig/lib/Mail/SpamAssassin/Conf/Parser.pm ./lib/Mail/SpamAssassin/Conf/Parser.pm --- ../Mail-SpamAssassin-4.0.0.orig/lib/Mail/SpamAssassin/Conf/Parser.pm 2022-12-14 08:03:20.000000000 +0200 +++ ./lib/Mail/SpamAssassin/Conf/Parser.pm 2023-01-22 19:56:21.930686000 +0200 @@ -1374,13 +1374,14 @@ } else { # $hdr used in eval text, validate carefully # check :addr etc header options - if ($text !~ /^([\w.-]+(?:\:|(?:\:[a-z]+){1,2})?)\s*([=!]~)\s*(.+)$/) { +# if ($text !~ /^([\w.-]+(?:\:|(?:\:[a-z]+){1,2})?)\s*([=!]~)\s*(.+)$/) { + if ($text !~ /^([\w.-]+(?:\:|(?:\:[a-z]+){1,2})?(?:\|[\w.-]+(?:\:|(?:\:[a-z]+){1,2})?)*)\s*([=!]~)\s*(.+)$/) { $self->lint_warn("config: invalid head test $name: $text"); return; } my ($hdr, $op, $pat) = ($1, $2, $3); $hdr =~ s/:$//; - if ($hdr =~ /:(?!(?:raw|addr|name|host|domain|ip|revip|first|last)\b)/i) { + if ($hdr =~ /:(?!(?:raw|addr|name|host|domain|ip|revip|first|last|case)\b)/i) { $self->lint_warn("config: invalid header modifier for $name: $hdr", $name); return; } diff -urN ../Mail-SpamAssassin-4.0.0.orig/lib/Mail/SpamAssassin/Message/Node.pm ./lib/Mail/SpamAssassin/Message/Node.pm --- ../Mail-SpamAssassin-4.0.0.orig/lib/Mail/SpamAssassin/Message/Node.pm 2022-12-14 08:03:21.000000000 +0200 +++ ./lib/Mail/SpamAssassin/Message/Node.pm 2023-01-22 13:02:27.228344000 +0200 @@ -64,6 +64,8 @@ my $self = { headers => {}, raw_headers => {}, + headers_case => {}, + raw_headers_case => {}, header_order => [] }; @@ -161,6 +163,10 @@ return unless defined $rawkey; + # Trim whitespace off of the header keys + $rawkey =~ s/^\s+//; + $rawkey =~ s/\s+$//; + # we're going to do things case insensitively my $key = lc($rawkey); @@ -186,6 +192,13 @@ push @{ $self->{'raw_headers'}->{$key} }, $raw_value; + if ( !exists $self->{'headers_case'}->{$rawkey} ) { + $self->{'headers_case'}->{$rawkey} = []; + $self->{'raw_headers_case'}->{$rawkey} = []; + } + push @{ $self->{'headers_case'}->{$rawkey} }, _decode_header($dec_value,$key); + push @{ $self->{'raw_headers_case'}->{$rawkey} }, $raw_value; + return $self->{'headers'}->{$key}->[-1]; } @@ -199,6 +212,24 @@ } } +sub header_case { + my $self = shift; + my $key = shift; + + # Trim whitespace off of the header keys + $key =~ s/^\s+//; + $key =~ s/\s+$//; + + if (wantarray) { + return unless exists $self->{'headers_case'}->{$key}; + return @{ $self->{'headers_case'}->{$key} }; + } + else { + return '' unless exists $self->{'headers_case'}->{$key}; + return $self->{'headers_case'}->{$key}->[-1]; + } +} + =item raw_header() Retrieves the raw version of headers from a specific MIME part. The only @@ -234,6 +265,24 @@ } } +sub raw_header_case { + my $self = shift; + my $key = shift; + + # Trim whitespace off of the header keys + $key =~ s/^\s+//; + $key =~ s/\s+$//; + + if (wantarray) { + return unless exists $self->{'raw_headers_case'}->{$key}; + return @{ $self->{'raw_headers_case'}->{$key} }; + } + else { + return '' unless exists $self->{'raw_headers_case'}->{$key}; + return $self->{'raw_headers_case'}->{$key}->[-1]; + } +} + =item add_body_part() Adds a Node child object to the current node object. @@ -920,6 +969,8 @@ foreach ( grep(/^${hdr}$/io, keys %{$self->{'headers'}}) ) { delete $self->{'headers'}->{$_}; delete $self->{'raw_headers'}->{$_}; + delete $self->{'headers_case'}->{$_}; + delete $self->{'raw_headers_case'}->{$_}; } my @neworder = grep(!/^${hdr}$/io, @{$self->{'header_order'}}); @@ -1090,6 +1141,35 @@ } else { if (@hdrs = $self->header($hdr)) { + $_ .= "\n" for @hdrs; + } + } + + if (wantarray) { + return @hdrs; + } + else { + return @hdrs ? $hdrs[-1] : undef; + } +} + +sub get_header_case { + my ($self, $hdr, $raw) = @_; + $raw ||= 0; + + # And now pick up all the entries into a list + # This is assumed to include a newline at the end ... + # This is also assumed to have removed continuation bits ... + + # Deal with the possibility that header() or raw_header() returns undef + my @hdrs; + if ( $raw ) { + if (@hdrs = $self->raw_header_case($hdr)) { + s/\015?\012\s+/ /gs for @hdrs; + } + } + else { + if (@hdrs = $self->header_case($hdr)) { $_ .= "\n" for @hdrs; } } diff -urN ../Mail-SpamAssassin-4.0.0.orig/lib/Mail/SpamAssassin/PerMsgStatus.pm ./lib/Mail/SpamAssassin/PerMsgStatus.pm --- ../Mail-SpamAssassin-4.0.0.orig/lib/Mail/SpamAssassin/PerMsgStatus.pm 2022-12-14 08:03:21.000000000 +0200 +++ ./lib/Mail/SpamAssassin/PerMsgStatus.pm 2023-01-22 19:56:57.857256000 +0200 @@ -2229,10 +2229,33 @@ sub _get { my ($self, $request) = @_; + if ($request =~ /\|/) { + my(@res); + foreach my $subrequest (split(/\|/, $request)) { + my $getcase = ($subrequest =~ s/:case//); + my $getraw = ( + $subrequest =~ s/:raw$// + || $subrequest eq 'ALL' + || $subrequest eq 'ALL-TRUSTED' + || $subrequest eq 'ALL-UNTRUSTED' + || $subrequest eq 'ALL-INTERNAL' + || $subrequest eq 'ALL-EXTERNAL' + ); + push(@res, '|') if ($#res >= 0); + if ($getcase) { + push(@res, $self->{msg}->get_header_case($subrequest, $getraw)); + } else { + push(@res, $self->{msg}->get_header($subrequest, $getraw)); + } + } + return(\@res); + } + my @results; my $getaddr = 0; my $getname = 0; my $getraw = 0; + my $getcase = 0; my $needraw = 0; my $gethost = 0; my $getdomain = 0; @@ -2254,6 +2277,7 @@ elsif ($1 eq 'revip') { $getip = $getrevip = 1 } elsif ($1 eq 'first') { $getfirst = 1 } elsif ($1 eq 'last') { $getlast = 1 } + elsif ($1 eq 'case') { $getcase = 1 } } } my $request_lc = lc $request; @@ -2336,8 +2360,19 @@ } # a conventional header else { - my @res = $getraw||$needraw ? $self->{msg}->raw_header($request) - : $self->{msg}->get_header($request); +# my @res = $getraw||$needraw ? $self->{msg}->raw_header($request) +# : $self->{msg}->get_header($request); + my(@res); + if ($getcase and ($getraw or $needraw)) { + @res = $self->{msg}->raw_header_case($request); + } elsif ($getcase and !($getraw or $needraw)) { + @res = $self->{msg}->get_header_case($request); + } elsif (!$getcase and ($getraw or $needraw)) { + @res = $self->{msg}->raw_header($request); + } elsif (!$getcase and !($getraw or $needraw)) { + @res = $self->{msg}->get_header($request); + } + if (!@res) { if (defined(my $m = $self->{msg}->get_metadata($request))) { push @res, $m;