File Coverage

lib/Sisimai/Lhost/Mimecast.pm
Criterion Covered Total %
statement 57 59 96.6
branch 25 34 73.5
condition 12 19 63.1
subroutine 6 6 100.0
pod 2 2 100.0
total 102 120 85.0


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Mimecast;
2 3     3   1975 use parent 'Sisimai::Lhost';
  3         5  
  3         24  
3 3     3   270 use v5.26;
  3         12  
4 3     3   15 use strict;
  3         636  
  3         87  
5 3     3   16 use warnings;
  3         4  
  3         2253  
6              
7 1     1 1 4 sub description { 'Mimecast' }
8             sub inquire {
9             # Detect an error from Mimecast: https://www.mimecast.com/
10             # @param [Hash] mhead Message headers of a bounce email
11             # @param [String] mbody Message body of a bounce email
12             # @return [Hash] Bounce data list and message/rfc822 part
13             # @return [undef] failed to decode or the arguments are missing
14             # @since v5.5.0
15 12     12 1 2125 my $class = shift;
16 12   100     68 my $mhead = shift // return undef;
17 11   100     32 my $mbody = shift // return undef;
18              
19             # Subject: [Postmaster] Email Delivery Failure
20 10 50 50     15 my $match = 0; $match ||= 1 if index($mhead->{'subject'}, 'Email Delivery Failure') > -1;
  10         78  
21 10 50       31 if( defined $mhead->{'message-id'} ) {
22             # Message-Id: <0123456789-1102117314000@us-mta-25.us.mimecast.lan>
23 10 50 50     42 $match ||= 1 if index($mhead->{'message-id'}, '.mimecast.lan>') > -1;
24             }
25 10 50       26 return undef unless $match;
26              
27 10         21 state $indicators = __PACKAGE__->INDICATORS;
28 10         14 state $boundaries = ['Content-Type: :message/rfc822']; # No such line in lhost-mimecast-*
29 10         32 state $startingof = {'message' => ['-- ']};
30              
31 10         89 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
32 10         107 my $permessage = {}; # (Hash) Store values of each Per-Message field
33 10         51 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  10         19  
34 10         40 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
35 10         15 my $readcursor = 0; # (Integer) Points the current cursor position
36 10         16 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
37              
38 10         115 for my $e ( split("\n", $emailparts->[0]) ) {
39             # Read error messages and delivery status lines from the head of the email to the previous
40             # line of the beginning of the original message.
41 240 100       380 unless( $readcursor ) {
42             # Beginning of the bounce message or message/delivery-status part
43 35 100       79 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) > -1;
44             }
45 240 100 100     658 next if ($readcursor & $indicators->{'deliverystatus'}) == 0 || $e eq "";
46              
47 165         195 $v = $dscontents->[-1];
48 165 100       292 if( index($e, '-- ') == 0 ) {
49             # An email that you attempted to send to the following address could not be delivered:
50             # -- sabineko@neko.ef.example.org
51 30 100       57 my $cv = substr($e, 3,); if( index($cv, " ") < 0 ) {
  30         51  
52             # -- sotoneko@cat.example.com
53 10 50       23 if( $v->{'recipient'} ) {
54             # There are multiple recipient addresses in the message body.
55 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
56 0         0 $v = $dscontents->[-1];
57             }
58 10         84 $v->{'recipient'} = Sisimai::Address->s3s4($cv);
59 10         21 $recipients++;
60              
61             } else {
62             # Deal each line begins with "-- " as an error message.
63             # The problem appears to be :
64             # -- Recipient email address is possibly incorrect
65             # Additional information follows :
66             # -- 5.4.1 Recipient address rejected: Access denied.
67 20         52 $v->{'diagnosis'} .= $cv.' ';
68             }
69             } else {
70             # Lines after Content-Type: message/delivery-status
71 135 100       283 if( my $f = Sisimai::RFC1894->match($e) ) {
72             # $e matched with any field defined in RFC3464
73 40 50       90 next unless my $o = Sisimai::RFC1894->field($e);
74              
75 40 100       82 if( $o->[3] eq 'code' ) {
76             # Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
77 5         11 $v->{'spec'} = $o->[1];
78 5         24 $v->{'diagnosis'} = $o->[2];
79              
80             } else {
81             # Other DSN fields defined in RFC3464
82 35 50       70 next unless exists $fieldtable->{ $o->[0] };
83 35 50 66     102 next if $o->[3] eq "host" && Sisimai::RFC1123->is_internethost($o->[2]) == 0;
84 35         87 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
85              
86 35 100       83 next unless $f == 1;
87 10         39 $permessage->{ $fieldtable->{ $o->[0] } } = $o->[2];
88             }
89             }
90             }
91             }
92 10 50       80 return undef unless $recipients;
93              
94 10         19 for my $e ( @$dscontents ) {
95             # Set default values if each value is empty.
96 10   0     525 $e->{ $_ } ||= $permessage->{ $_ } || '' for keys %$permessage;
      33        
97 10         43 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
98             }
99 10         69 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
100             }
101              
102             1;
103             __END__