File Coverage

lib/Mail/DMARC/Report/Aggregate.pm
Criterion Covered Total %
statement 111 123 90.2
branch 33 46 71.7
condition 12 15 80.0
subroutine 16 17 94.1
pod 0 10 0.0
total 172 211 81.5


line stmt bran cond sub pod time code
1             package Mail::DMARC::Report::Aggregate;
2 12     12   59138 use strict;
  12         31  
  12         453  
3 12     12   94 use warnings;
  12         27  
  12         566  
4              
5             our $VERSION = '1.20210927';
6              
7 12     12   74 use Carp;
  12         28  
  12         780  
8 12     12   695 use Data::Dumper;
  12         6142  
  12         827  
9 12     12   7692 use XML::LibXML;
  12         387468  
  12         98  
10              
11 12     12   3367 use parent 'Mail::DMARC::Base';
  12         625  
  12         107  
12 12     12   7023 use Mail::DMARC::Report::Aggregate::Metadata;
  12         131  
  12         17295  
13              
14             sub metadata {
15 174     174 0 1195 my $self = shift;
16 174 100       883 return $self->{metadata} if ref $self->{metadata};
17 19         161 return $self->{metadata} = Mail::DMARC::Report::Aggregate::Metadata->new;
18             }
19              
20             sub policy_published {
21 65     65 0 4250 my ( $self, $policy ) = @_;
22 65 100       314 return $self->{policy_published} if ! $policy;
23 16 50       68 croak "not a policy object!" if 'Mail::DMARC::Policy' ne ref $policy;
24 16         80 return $self->{policy_published} = $policy;
25             }
26              
27             sub record { ## no critic (Ambiguous)
28 35     35 0 866 my ($self, $record, @extra) = @_;
29 35 100       107 if ( !$record) {
30 19   100     187 return $self->{record} || [];
31             }
32              
33 16 50       47 if (@extra) { croak "invalid args"; }
  0         0  
34              
35 16 50       54 if ('Mail::DMARC::Report::Aggregate::Record' ne ref $record) {
36 0         0 croak "not a record object";
37             }
38              
39 16   100     114 $self->{record} ||= [];
40              
41 16         24 push @{ $self->{record} }, $record;
  16         43  
42              
43 16         64 return $self->{record};
44             };
45              
46             sub dump_report {
47 0     0 0 0 my $self = shift;
48 0         0 carp Dumper( $self->{metadata}, $self->{policy_published}, $self->{record} );
49 0         0 return;
50             }
51              
52             sub as_xml {
53 5     5 0 12 my $self = shift;
54 5         16 my $meta = $self->metadata->as_xml;
55 5         24 my $pubp = $self->get_policy_published_as_xml;
56 5         17 my $reco = $self->get_record_as_xml;
57              
58             return <<"EO_XML"
59            
60            
61             \t1.0
62             $meta
63             $pubp
64             $reco
65             EO_XML
66 5         39 ;
67             }
68              
69             sub get_record_as_xml {
70 6     6 0 12 my $self = shift;
71              
72 6         9 my $rec_xml = '';
73 6         9 foreach my $rec ( @{ $self->{record} } ) {
  6         16  
74 8         15 $rec_xml .= "\t\n";
75 8 50       27 my $ip = $rec->{row}{source_ip} or croak "no source IP!?";
76 8 50       19 my $count = $rec->{row}{count} or croak "no count!?";
77 8 50       22 $rec->{row}{policy_evaluated}{disposition} or croak "no disposition?";
78 8         79 $ip = XML::LibXML::Text->new( $ip )->toString();
79 8         70 $count = XML::LibXML::Text->new( $count )->toString();
80 8         56 $rec_xml
81             .="\t\t\n"
82             . "\t\t\t$ip\n"
83             . "\t\t\t$count\n"
84             . $self->get_policy_evaluated_as_xml( $rec )
85             . "\t\t\n"
86             . $self->get_identifiers_as_xml($rec)
87             . $self->get_auth_results_as_xml($rec);
88 8         24 $rec_xml .= "\t\n";
89             }
90 6         20 return $rec_xml;
91             }
92              
93             sub get_identifiers_as_xml {
94 8     8 0 18 my ( $self, $rec ) = @_;
95 8         23 my $id = "\t\t\n";
96 8         19 foreach my $f (qw/ envelope_to envelope_from header_from /) {
97 24 100       67 if ( $f eq 'header_from' ) { # min occurs = 1
    100          
    50          
98 8 50       25 croak "missing header_from!" if ! $rec->{identifiers}{$f};
99             }
100             elsif ( $f eq 'envelope_from') { # min occurs = 1
101 8 50       19 $rec->{identifiers}{$f} = '' if ! $rec->{identifiers}{$f};
102             }
103             elsif ( $f eq 'envelope_to' ) { # min occurs = 0
104 8 100       24 next if ! $rec->{identifiers}{$f};
105             };
106              
107 20         158 my $val = XML::LibXML::Text->new( $rec->{identifiers}{$f} )->toString();
108 20         107 $id .= "\t\t\t<$f>$val\n";
109             }
110 8         18 $id .= "\t\t\n";
111 8         33 return $id;
112             }
113              
114             sub get_auth_results_as_xml {
115 8     8 0 16 my ( $self, $rec ) = @_;
116 8         15 my $ar = "\t\t\n";
117              
118 8         13 foreach my $dkim_sig ( @{ $rec->{auth_results}{dkim} } ) {
  8         21  
119 4         8 $ar .= "\t\t\t\n";
120 4         10 foreach my $g (qw/ domain selector result human_result /) {
121 16 50       41 next if !defined $dkim_sig->{$g};
122 16         129 my $val = XML::LibXML::Text->new( $dkim_sig->{$g} )->toString();
123 16         83 $ar .= "\t\t\t\t<$g>$val\n";
124             }
125 4         11 $ar .= "\t\t\t\n";
126             }
127              
128 8         16 foreach my $spf ( @{ $rec->{auth_results}{spf} } ) {
  8         27  
129 8         15 $ar .= "\t\t\t\n";
130 8         16 foreach my $g (qw/ domain scope result /) {
131 24 50       51 next if !defined $spf->{$g};
132 24         154 my $val = XML::LibXML::Text->new( $spf->{$g} )->toString();
133 24         103 $ar .= "\t\t\t\t<$g>$val\n";
134             }
135 8         22 $ar .= "\t\t\t\n";
136             }
137              
138 8         15 $ar .= "\t\t\n";
139 8         23 return $ar;
140             }
141              
142             sub get_policy_published_as_xml {
143 6     6 0 11 my $self = shift;
144 6 50       17 my $pp = $self->policy_published or return '';
145 6         18 my $xml = "\t\n\t\t$pp->{domain}\n";
146 6         16 foreach my $f (qw/ adkim aspf p sp pct fo /) {
147 36         61 my $v = $pp->{$f};
148             # Set some default values for missing optional fields if necessary
149 36 100 66     89 if ( $f eq 'sp' && !defined $v ) {
150 6         11 $v = $pp->{'p'};
151             }
152 36 100 66     78 if ( $f eq 'pct' && !defined $v ) {
153 6         9 $v = '100';
154             }
155 36 100 100     71 if ( $f eq 'fo' && !defined $v ) {
156 4         7 $v = '0';
157             }
158 36 100       70 next if !defined $v;
159 24         200 $v = XML::LibXML::Text->new( $v )->toString();
160 24         124 $xml .= "\t\t<$f>$v\n";
161             }
162 6         15 $xml .= "\t";
163 6         17 return $xml;
164             }
165              
166             sub get_policy_evaluated_as_xml {
167 8     8 0 18 my ( $self, $rec ) = @_;
168 8         14 my $pe = "\t\t\t\n";
169              
170 8         50 foreach my $f (qw/ disposition dkim spf /) {
171 24         189 my $val = XML::LibXML::Text->new( $rec->{row}{policy_evaluated}{$f} )->toString();
172 24         164 $pe .= "\t\t\t\t<$f>$val\n";
173             }
174              
175 8         18 my $reasons = $rec->{row}{policy_evaluated}{reason};
176 8 50 50     75 if ( $reasons && scalar @$reasons ) {
177 0         0 foreach my $reason ( @$reasons ) {
178 0         0 my $typeval = XML::LibXML::Text->new( $reason->{type} )->toString();
179 0         0 my $commentval = XML::LibXML::Text->new( $reason->{comment} )->toString();
180 0         0 $pe .= "\t\t\t\t\n";
181 0         0 $pe .= "\t\t\t\t\t$typeval\n";
182 0         0 $pe .= "\t\t\t\t\t$commentval\n";
183 0         0 $pe .= "\t\t\t\t\n";
184             }
185             };
186 8         23 $pe .= "\t\t\t\n";
187 8         35 return $pe;
188             }
189              
190             1;
191              
192             __END__