File Coverage

lib/Mail/DMARC/Policy.pm
Criterion Covered Total %
statement 115 115 100.0
branch 87 100 87.0
condition 12 18 66.6
subroutine 23 23 100.0
pod 14 19 73.6
total 251 275 91.2


line stmt bran cond sub pod time code
1             package Mail::DMARC::Policy;
2 12     12   264529 use strict;
  12         48  
  12         382  
3 12     12   69 use warnings;
  12         25  
  12         524  
4              
5             our $VERSION = '1.20210927';
6              
7 12     12   70 use Carp;
  12         23  
  12         783  
8              
9 12     12   4831 use Mail::DMARC::Report::URI;
  12         32  
  12         22737  
10              
11             sub new {
12 82     82 1 17918 my ( $class, @args ) = @_;
13 82 50       224 my $package = ref $class ? ref $class : $class;
14 82         190 my $self = bless {}, $package;
15              
16 82 100       282 return $self if 0 == scalar @args; # no args, empty pol
17 71 100       186 if (1 == @args) { # a string
18 55         151 my $policy = $self->parse( $args[0] );
19 55         186 $self->is_valid($policy);
20 53         265 return $policy;
21             }
22              
23 16 50       49 croak "invalid arguments" if @args % 2 != 0;
24 16         69 my $policy = {@args};
25 16         28 bless $policy, $package;
26 16 50       49 croak "invalid policy" if !$self->is_valid($policy);
27 14         86 return bless $policy, $package;
28             }
29              
30             sub parse {
31 75     75 1 2405 my ( $self, $str, @junk ) = @_;
32 75 50       209 croak "invalid parse request" if 0 != scalar @junk;
33 75         125 my $cleaned = $str;
34 75         492 $cleaned =~ s/\s//g; # remove whitespace
35 75         204 $cleaned =~ s/\\;/;/g; # replace \; with ;
36 75         187 $cleaned =~ s/;;/;/g; # replace ;; with ;
37 75         160 $cleaned =~ s/;0;/;/g; # replace ;0; with ;
38 75 100       268 chop $cleaned if ';' eq substr $cleaned, -1, 1; # remove a trailing ;
39 75         361 my @tag_vals = split /;/, $cleaned;
40 75         117 my %policy;
41 75         128 my $warned = 0;
42 75         171 foreach my $tv (@tag_vals) {
43 482         1810 my ($tag, $value) = split /=|:|-/, $tv, 2;
44 482 100 33     2141 if ( !defined $tag || !defined $value || $value eq '') {
      66        
45 2 50       6 if (!$warned) {
46             #warn "tv: $tv\n";
47 2         89 warn "invalid DMARC record, please post this message to\n" .
48             "\thttps://github.com/msimerson/mail-dmarc/issues/39\n" .
49             "\t$str\n";
50             }
51 2         8 $warned++;
52 2         6 next;
53             }
54 480         1115 $policy{lc $tag} = $value;
55             }
56 75         353 return bless \%policy, ref $self; # inherited defaults + overrides
57             }
58              
59             sub apply_defaults {
60 7     7 1 1116 my $self = shift;
61              
62 7 100       29 $self->adkim('r') if !defined $self->adkim;
63 7 100       29 $self->aspf('r') if !defined $self->aspf;
64 7 50       30 $self->fo(0) if !defined $self->fo;
65 7 50       6035 $self->ri(86400) if !defined $self->ri;
66 7 50       26 $self->rf('afrf') if !defined $self->rf;
67              
68             # pct # default is 100%, but 100% -vs- not defined is different
69 7         19 return 1;
70             }
71              
72             sub v {
73 6 100   6 1 1337 return $_[0]->{v} if 1 == scalar @_;
74 4 100       188 croak "unsupported DMARC version" if 'DMARC1' ne uc $_[1];
75 3         13 return $_[0]->{v} = $_[1];
76             }
77              
78             sub p {
79 37 100   37 1 2985 return $_[0]->{p} if 1 == scalar @_;
80 9 100       20 croak "invalid p" if !$_[0]->is_valid_p( $_[1] );
81 6         25 return $_[0]->{p} = $_[1];
82             }
83              
84             sub sp {
85 24 100   24 1 3016 return $_[0]->{sp} if 1 == scalar @_;
86 9 100       21 croak "invalid sp ($_[1])" if !$_[0]->is_valid_p( $_[1] );
87 6         26 return $_[0]->{sp} = $_[1];
88             }
89              
90             sub adkim {
91 42 100   42 1 2097 return $_[0]->{adkim} if 1 == scalar @_;
92 12 100       29 croak "invalid adkim" if 0 == grep {/^\Q$_[1]\E$/ix} qw/ r s /;
  24         374  
93 10         44 return $_[0]->{adkim} = $_[1];
94             }
95              
96             sub aspf {
97 46 100   46 1 1815 return $_[0]->{aspf} if 1 == scalar @_;
98 13 100       30 croak "invalid aspf" if 0 == grep {/^\Q$_[1]\E$/ix} qw/ r s /;
  26         352  
99 11         47 return $_[0]->{aspf} = $_[1];
100             }
101              
102             sub fo {
103 26 100   26 1 3784 return $_[0]->{fo} if 1 == scalar @_;
104 19 100       400 croak "invalid fo: $_[1]" if $_[1] !~ /^[01ds](:[01ds])*$/ix;
105 15         55 return $_[0]->{fo} = $_[1];
106             }
107              
108             sub rua {
109 36 100   36 1 1488 return $_[0]->{rua} if 1 == scalar @_;
110 17 100       69 croak "invalid rua" if !$_[0]->is_valid_uri_list( $_[1] );
111 15         86 return $_[0]->{rua} = $_[1];
112             }
113              
114             sub ruf {
115 2 50   2 1 629 return $_[0]->{ruf} if 1 == scalar @_;
116 2 100       5 croak "invalid rua" if !$_[0]->is_valid_uri_list( $_[1] );
117 1         8 return $_[0]->{ruf} = $_[1];
118             }
119              
120             sub rf {
121 21 100   21 1 2322 return $_[0]->{rf} if 1 == scalar @_;
122 14         51 foreach my $f ( split /,/, $_[1] ) {
123 14 100       38 croak "invalid format: $f" if !$_[0]->is_valid_rf($f);
124             }
125 11         42 return $_[0]->{rf} = $_[1];
126             }
127              
128             sub ri {
129 26 100   26 1 2274 return $_[0]->{ri} if 1 == scalar @_;
130 14 100       218 croak "not numeric ($_[1])!" if $_[1] =~ /\D/;
131 12 50       39 croak "not an integer!" if $_[1] != int $_[1];
132 12 100 66     137 croak "out of range" if ( $_[1] < 0 || $_[1] > 4294967295 );
133 11         47 return $_[0]->{ri} = $_[1];
134             }
135              
136             sub pct {
137 20 100   20 1 3703 return $_[0]->{pct} if 1 == scalar @_;
138 11 100       399 croak "not numeric ($_[1])!" if $_[1] =~ /\D/;
139 6 50       11 croak "not an integer!" if $_[1] != int $_[1];
140 6 100 66     93 croak "out of range" if $_[1] < 0 || $_[1] > 100;
141 5         21 return $_[0]->{pct} = $_[1];
142             }
143              
144             sub domain {
145 62 100   62 0 444 return $_[0]->{domain} if 1 == scalar @_;
146 7         46 return $_[0]->{domain} = $_[1];
147             }
148              
149             sub is_valid_rf {
150 19     19 0 1216 my ( $self, $f ) = @_;
151 19 100       35 return ( grep {/^\Q$f\E$/i} qw/ iodef afrf / ) ? 1 : 0;
  38         542  
152             }
153              
154             sub is_valid_p {
155 140     140 0 2034 my ( $self, $p ) = @_;
156 140 50       269 croak "unspecified p" if !defined $p;
157 140 100       238 return ( grep {/^\Q$p\E$/i} qw/ none reject quarantine / ) ? 1 : 0;
  420         2825  
158             }
159              
160             sub is_valid_uri_list {
161 21     21 0 55 my ( $self, $str ) = @_;
162 21   66     157 $self->{uri} ||= Mail::DMARC::Report::URI->new;
163 21         81 my $uris = $self->{uri}->parse($str);
164 21         341 return scalar @$uris;
165             }
166              
167             sub is_valid {
168 109     109 0 286 my ( $self, $obj ) = @_;
169 109 100       272 $obj = $self if !$obj;
170 109 100       332 croak "missing version specifier" if !$obj->{v};
171 108 50       290 croak "invalid version" if 'DMARC1' ne uc $obj->{v};
172 108 100       200 if ( !$obj->{p} ) {
173 4 100 100     13 if ( $obj->{rua} && $self->is_valid_uri_list( $obj->{rua} ) ) {
174 1         2 $obj->{p} = 'none';
175             }
176             else {
177 3         296 croak "missing policy action (p=)";
178             }
179             }
180 105 100       248 croak "invalid policy action" if !$self->is_valid_p( $obj->{p} );
181              
182             # everything else is optional
183 104         252 return 1;
184             }
185              
186             1;
187              
188             __END__