You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by fk...@apache.org on 2024/10/21 20:43:18 UTC
svn commit: r1921469 - /spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDetail.pm
Author: fkento
Date: Mon Oct 21 20:43:18 2024
New Revision: 1921469
URL: http://svn.apache.org/viewvc?rev=1921469&view=rev
Log:
Bug 8283: Negating matches in URIDetail.pm
Modified:
spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDetail.pm
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDetail.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDetail.pm?rev=1921469&r1=1921468&r2=1921469&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDetail.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDetail.pm Mon Oct 21 20:43:18 2024
@@ -66,6 +66,34 @@ Example rule to look for suspicious "htt
Regular expressions should be delimited by slashes.
+Negating matches is supported in SpamAssassin 4.0.2 or higher by prefixing the key with an exclamation mark.
+
+Example rule to look for links where the text contains "id.me" but the host is not "id.me":
+
+ uri_detail FAKE_ID_ME text =~ /\bid\.me\b/ !host =~ /^id\.me$/
+
+The difference between '!key =~ ...' and 'key !~ ...' is due to the fact that keys can contain multiple values.
+'!key =~ ...' will be true if none of the values match the regex, while 'key !~ ...' will be true if any of the values
+do not match the regex.
+
+For example, consider the following data structure:
+
+ {
+ host => {
+ 'id.me' => 1,
+ 'example.com' => 1,
+ },
+ text => [
+ 'Login to ID.me'
+ ]
+ }
+
+The rule 'host !~ /^id\.me$/' would be true, because 'example.com' does not match the regex.
+The rule '!host =~ /^id\.me$/' would be false, because it is the logical negation of 'host =~ /^id\.me$/'
+which is true because 'id.me' matches the regex.
+
+There must not be any whitespace between the key and the negation operator.
+
=cut
package Mail::SpamAssassin::Plugin::URIDetail;
@@ -117,12 +145,13 @@ sub set_config {
my $added_criteria = 0;
# if this matches a regex, it strips slashes
- while ($def =~ m{\b(\w+)\b\s*([\=\!]\~)\s*((?:/.*?/|m(\W).*?\4)[imsx]*)(?=\s|$)}g) {
+ while ($def =~ m{\s*([!\w]+)\b\s*([\=\!]\~)\s*((?:/.*?/|m(\W).*?\4)[imsx]*)(?=\s|$)}g) {
my $target = $1;
my $op = $2;
my $pattern = $3;
+ my $neg = 0;
- if ($target !~ /^(?:raw|type|cleaned|text|domain|host)$/) {
+ if ($target !~ /^!?(?:raw|type|cleaned|text|domain|host)$/) {
return $Mail::SpamAssassin::Conf::INVALID_VALUE;
}
@@ -133,8 +162,12 @@ sub set_config {
}
dbg("config: uri_detail adding ($target $op /$rec/) to $name");
+ if ($target =~ /^!(.*)/) {
+ $target = $1;
+ $neg = 1;
+ }
$conf->{parser}->{conf}->{uri_detail}->{$name}->{$target} =
- [$op, $rec];
+ [$op, $rec, $neg];
$added_criteria = 1;
}
@@ -165,73 +198,99 @@ sub check_uri_detail {
dbg("uri: running uri_detail $test: $raw");
if (exists $rule->{raw}) {
- my($op,$patt) = @{$rule->{raw}};
+ my($op,$patt,$neg) = @{$rule->{raw}};
+ my $match = 0;
if ( ($op eq '=~' && $raw =~ $patt) ||
($op eq '!~' && $raw !~ $patt) ) {
- dbg("uri: raw matched: '%s' %s /%s/", $raw,$op,$patt);
- } else {
- next;
+ $match = 1;
}
+ $match = 1-$match if $neg;
+ next unless $match;
}
if (exists $rule->{type}) {
next unless $info->{types};
- my($op,$patt) = @{$rule->{type}};
+ my($op,$patt,$neg) = @{$rule->{type}};
my $match;
for my $text (keys %{ $info->{types} }) {
if ( ($op eq '=~' && $text =~ $patt) ||
($op eq '!~' && $text !~ $patt) ) { $match = $text; last }
}
- next unless defined $match;
- dbg("uri: type matched: '%s' %s /%s/", $match,$op,$patt);
+ if ( $neg ) {
+ next if defined $match;
+ dbg("uri: type negative matched: %s /%s/", $op,$patt);
+ } else {
+ next unless defined $match;
+ dbg("uri: type matched: '%s' %s /%s/", $match,$op,$patt);
+ }
}
if (exists $rule->{cleaned}) {
next unless $info->{cleaned};
- my($op,$patt) = @{$rule->{cleaned}};
+ my($op,$patt,$neg) = @{$rule->{cleaned}};
my $match;
for my $text (@{ $info->{cleaned} }) {
if ( ($op eq '=~' && $text =~ $patt) ||
($op eq '!~' && $text !~ $patt) ) { $match = $text; last }
}
- next unless defined $match;
- dbg("uri: cleaned matched: '%s' %s /%s/", $match,$op,$patt);
+ if ( $neg ) {
+ next if defined $match;
+ dbg("uri: cleaned negative matched: %s /%s/", $op,$patt);
+ } else {
+ next unless defined $match;
+ dbg("uri: cleaned matched: '%s' %s /%s/", $match,$op,$patt);
+ }
}
if (exists $rule->{text}) {
next unless $info->{anchor_text};
- my($op,$patt) = @{$rule->{text}};
+ my($op,$patt,$neg) = @{$rule->{text}};
my $match;
for my $text (@{ $info->{anchor_text} }) {
if ( ($op eq '=~' && $text =~ $patt) ||
($op eq '!~' && $text !~ $patt) ) { $match = $text; last }
}
- next unless defined $match;
- dbg("uri: text matched: '%s' %s /%s/", $match,$op,$patt);
+ if ( $neg ) {
+ next if defined $match;
+ dbg("uri: text negative matched: %s /%s/", $op,$patt);
+ } else {
+ next unless defined $match;
+ dbg("uri: text matched: '%s' %s /%s/", $match,$op,$patt);
+ }
}
if (exists $rule->{domain}) {
next unless $info->{domains};
- my($op,$patt) = @{$rule->{domain}};
+ my($op,$patt,$neg) = @{$rule->{domain}};
my $match;
for my $text (keys %{ $info->{domains} }) {
if ( ($op eq '=~' && $text =~ $patt) ||
($op eq '!~' && $text !~ $patt) ) { $match = $text; last }
}
- next unless defined $match;
- dbg("uri: domain matched: '%s' %s /%s/", $match,$op,$patt);
+ if ( $neg ) {
+ next if defined $match;
+ dbg("uri: domain negative matched: %s /%s/", $op,$patt);
+ } else {
+ next unless defined $match;
+ dbg("uri: domain matched: '%s' %s /%s/", $match,$op,$patt);
+ }
}
if (exists $rule->{host}) {
next unless $info->{hosts};
- my($op,$patt) = @{$rule->{host}};
+ my($op,$patt,$neg) = @{$rule->{host}};
my $match;
for my $text (keys %{ $info->{hosts} }) {
if ( ($op eq '=~' && $text =~ $patt) ||
($op eq '!~' && $text !~ $patt) ) { $match = $text; last }
}
- next unless defined $match;
- dbg("uri: host matched: '%s' %s /%s/", $match,$op,$patt);
+ if ( $neg ) {
+ next if defined $match;
+ dbg("uri: host negative matched: %s /%s/", $op,$patt);
+ } else {
+ next unless defined $match;
+ dbg("uri: host matched: '%s' %s /%s/", $match, $op, $patt);
+ }
}
dbg("uri: all criteria for $test met - HIT");
@@ -245,4 +304,6 @@ sub check_uri_detail {
sub has_host_key { 1 } # can match with "host" key
+sub has_uridetail_negate { 1 } # Negating matches (Bug 8283)
+
1;