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   295563 use strict;
  12         49  
  12         427  
3 12     12   77 use warnings;
  12         24  
  12         645  
4              
5             our $VERSION = '1.20211209';
6              
7 12     12   77 use Carp;
  12         26  
  12         817  
8              
9 12     12   5721 use Mail::DMARC::Report::URI;
  12         32  
  12         25169  
10              
11             sub new {
12 82     82 1 18456 my ( $class, @args ) = @_;
13 82 50       247 my $package = ref $class ? ref $class : $class;
14 82         189 my $self = bless {}, $package;
15              
16 82 100       375 return $self if 0 == scalar @args; # no args, empty pol
17 71 100       183 if (1 == @args) { # a string
18 55         157 my $policy = $self->parse( $args[0] );
19 55         197 $self->is_valid($policy);
20 53         248 return $policy;
21             }
22              
23 16 50       60 croak "invalid arguments" if @args % 2 != 0;
24 16         85 my $policy = {@args};
25 16         31 bless $policy, $package;
26 16 50       57 croak "invalid policy" if !$self->is_valid($policy);
27 14         100 return bless $policy, $package;
28             }
29              
30             sub parse {
31 75     75 1 2480 my ( $self, $str, @junk ) = @_;
32 75 50       189 croak "invalid parse request" if 0 != scalar @junk;
33 75         127 my $cleaned = $str;
34 75         462 $cleaned =~ s/\s//g; # remove whitespace
35 75         196 $cleaned =~ s/\\;/;/g; # replace \; with ;
36 75         142 $cleaned =~ s/;;/;/g; # replace ;; with ;
37 75         155 $cleaned =~ s/;0;/;/g; # replace ;0; with ;
38 75 100       277 chop $cleaned if ';' eq substr $cleaned, -1, 1; # remove a trailing ;
39 75         344 my @tag_vals = split /;/, $cleaned;
40 75         119 my %policy;
41 75         117 my $warned = 0;
42 75         178 foreach my $tv (@tag_vals) {
43 482         1716 my ($tag, $value) = split /=|:|-/, $tv, 2;
44 482 100 33     2085 if ( !defined $tag || !defined $value || $value eq '') {
      66        
45 2 50       6 if (!$warned) {
46             #warn "tv: $tv\n";
47 2         83 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         9 $warned++;
52 2         6 next;
53             }
54 480         1140 $policy{lc $tag} = $value;
55             }
56 75         331 return bless \%policy, ref $self; # inherited defaults + overrides
57             }
58              
59             sub apply_defaults {
60 7     7 1 569 my $self = shift;
61              
62 7 100       28 $self->adkim('r') if !defined $self->adkim;
63 7 100       5490 $self->aspf('r') if !defined $self->aspf;
64 7 50       20 $self->fo(0) if !defined $self->fo;
65 7 50       26 $self->ri(86400) if !defined $self->ri;
66 7 50       24 $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 1688 return $_[0]->{v} if 1 == scalar @_;
74 4 100       145 croak "unsupported DMARC version" if 'DMARC1' ne uc $_[1];
75 3         16 return $_[0]->{v} = $_[1];
76             }
77              
78             sub p {
79 37 100   37 1 3173 return $_[0]->{p} if 1 == scalar @_;
80 9 100       22 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 4170 return $_[0]->{sp} if 1 == scalar @_;
86 9 100       41 croak "invalid sp ($_[1])" if !$_[0]->is_valid_p( $_[1] );
87 6         34 return $_[0]->{sp} = $_[1];
88             }
89              
90             sub adkim {
91 42 100   42 1 2871 return $_[0]->{adkim} if 1 == scalar @_;
92 12 100       30 croak "invalid adkim" if 0 == grep {/^\Q$_[1]\E$/ix} qw/ r s /;
  24         427  
93 10         66 return $_[0]->{adkim} = $_[1];
94             }
95              
96             sub aspf {
97 46 100   46 1 2249 return $_[0]->{aspf} if 1 == scalar @_;
98 13 100       32 croak "invalid aspf" if 0 == grep {/^\Q$_[1]\E$/ix} qw/ r s /;
  26         377  
99 11         65 return $_[0]->{aspf} = $_[1];
100             }
101              
102             sub fo {
103 26 100   26 1 4116 return $_[0]->{fo} if 1 == scalar @_;
104 19 100       411 croak "invalid fo: $_[1]" if $_[1] !~ /^[01ds](:[01ds])*$/ix;
105 15         52 return $_[0]->{fo} = $_[1];
106             }
107              
108             sub rua {
109 36 100   36 1 1571 return $_[0]->{rua} if 1 == scalar @_;
110 17 100       73 croak "invalid rua" if !$_[0]->is_valid_uri_list( $_[1] );
111 15         82 return $_[0]->{rua} = $_[1];
112             }
113              
114             sub ruf {
115 2 50   2 1 661 return $_[0]->{ruf} if 1 == scalar @_;
116 2 100       5 croak "invalid rua" if !$_[0]->is_valid_uri_list( $_[1] );
117 1         6 return $_[0]->{ruf} = $_[1];
118             }
119              
120             sub rf {
121 21 100   21 1 2101 return $_[0]->{rf} if 1 == scalar @_;
122 14         47 foreach my $f ( split /,/, $_[1] ) {
123 14 100       40 croak "invalid format: $f" if !$_[0]->is_valid_rf($f);
124             }
125 11         41 return $_[0]->{rf} = $_[1];
126             }
127              
128             sub ri {
129 26 100   26 1 2405 return $_[0]->{ri} if 1 == scalar @_;
130 14 100       206 croak "not numeric ($_[1])!" if $_[1] =~ /\D/;
131 12 50       43 croak "not an integer!" if $_[1] != int $_[1];
132 12 100 66     156 croak "out of range" if ( $_[1] < 0 || $_[1] > 4294967295 );
133 11         46 return $_[0]->{ri} = $_[1];
134             }
135              
136             sub pct {
137 20 100   20 1 3730 return $_[0]->{pct} if 1 == scalar @_;
138 11 100       450 croak "not numeric ($_[1])!" if $_[1] =~ /\D/;
139 6 50       13 croak "not an integer!" if $_[1] != int $_[1];
140 6 100 66     92 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 460 return $_[0]->{domain} if 1 == scalar @_;
146 7         45 return $_[0]->{domain} = $_[1];
147             }
148              
149             sub is_valid_rf {
150 19     19 0 1211 my ( $self, $f ) = @_;
151 19 100       37 return ( grep {/^\Q$f\E$/i} qw/ iodef afrf / ) ? 1 : 0;
  38         600  
152             }
153              
154             sub is_valid_p {
155 140     140 0 2101 my ( $self, $p ) = @_;
156 140 50       304 croak "unspecified p" if !defined $p;
157 140 100       243 return ( grep {/^\Q$p\E$/i} qw/ none reject quarantine / ) ? 1 : 0;
  420         2994  
158             }
159              
160             sub is_valid_uri_list {
161 21     21 0 55 my ( $self, $str ) = @_;
162 21   66     188 $self->{uri} ||= Mail::DMARC::Report::URI->new;
163 21         84 my $uris = $self->{uri}->parse($str);
164 21         364 return scalar @$uris;
165             }
166              
167             sub is_valid {
168 109     109 0 293 my ( $self, $obj ) = @_;
169 109 100       275 $obj = $self if !$obj;
170 109 100       323 croak "missing version specifier" if !$obj->{v};
171 108 50       287 croak "invalid version" if 'DMARC1' ne uc $obj->{v};
172 108 100       241 if ( !$obj->{p} ) {
173 4 100 100     12 if ( $obj->{rua} && $self->is_valid_uri_list( $obj->{rua} ) ) {
174 1         3 $obj->{p} = 'none';
175             }
176             else {
177 3         306 croak "missing policy action (p=)";
178             }
179             }
180 105 100       242 croak "invalid policy action" if !$self->is_valid_p( $obj->{p} );
181              
182             # everything else is optional
183 104         258 return 1;
184             }
185              
186             1;
187              
188             __END__