File Coverage

lib/Sisimai/Lhost/Zoho.pm
Criterion Covered Total %
statement 58 63 92.0
branch 21 26 80.7
condition 3 4 75.0
subroutine 6 6 100.0
pod 2 2 100.0
total 90 101 89.1


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Zoho;
2 17     17   5214 use parent 'Sisimai::Lhost';
  17         29  
  17         81  
3 17     17   949 use feature ':5.10';
  17         28  
  17         1003  
4 17     17   80 use strict;
  17         34  
  17         317  
5 17     17   75 use warnings;
  17         25  
  17         11303  
6              
7 2     2 1 1054 sub description { 'Zoho Mail: https://www.zoho.com' }
8             sub make {
9             # Detect an error from Zoho Mail
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 parse or the arguments are missing
14             # @since v4.1.7
15 245     245 1 781 my $class = shift;
16 245   100     571 my $mhead = shift // return undef;
17 244   50     572 my $mbody = shift // return undef;
18              
19             # X-ZohoMail: Si CHF_MF_NL SS_10 UW48 UB48 FMWL UW48 UB48 SGR3_1_09124_42
20             # X-Zoho-Virus-Status: 2
21             # X-Mailer: Zoho Mail
22 244 100       647 return undef unless $mhead->{'x-zohomail'};
23              
24 26         64 state $indicators = __PACKAGE__->INDICATORS;
25 26         44 state $rebackbone = qr|^Received:[ ]*from[ ]mail[.]zoho[.]com[ ]by[ ]mx[.]zohomail[.]com|m;
26 26         35 state $startingof = { 'message' => ['This message was created automatically by mail delivery'] };
27 26         37 state $messagesof = { 'expired' => ['Host not reachable'] };
28              
29 26         69 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
30 26         91 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
31 26         48 my $readcursor = 0; # (Integer) Points the current cursor position
32 26         31 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
33 26         45 my $qprintable = 0;
34 26         40 my $v = undef;
35              
36 26         131 for my $e ( split("\n", $emailsteak->[0]) ) {
37             # Read error messages and delivery status lines from the head of the email
38             # to the previous line of the beginning of the original message.
39 250 100       317 unless( $readcursor ) {
40             # Beginning of the bounce message or message/delivery-status part
41 52 100       183 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
42 52         69 next;
43             }
44 198 50       257 next unless $readcursor & $indicators->{'deliverystatus'};
45 198 100       267 next unless length $e;
46              
47             # This message was created automatically by mail delivery software.
48             # A message that you sent could not be delivered to one or more of its recip=
49             # ients. This is a permanent error.=20
50             #
51             # kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.=
52             # 1 ... User Unknown
53              
54             # This message was created automatically by mail delivery software.
55             # A message that you sent could not be delivered to one or more of its recipients. This is a permanent error.
56             #
57             # shironeko@example.org Invalid Address, ERROR_CODE :550, ERROR_CODE :Requested action not taken: mailbox unavailable
58 137         146 $v = $dscontents->[-1];
59              
60 137 100       384 if( $e =~ /\A([^ ]+[@][^ ]+)[ \t]+(.+)\z/ ) {
    100          
61             # kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.=
62 26 100       77 if( $v->{'recipient'} ) {
63             # There are multiple recipient addresses in the message body.
64 5         20 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
65 5         14 $v = $dscontents->[-1];
66             }
67 26         63 $v->{'recipient'} = $1;
68 26         51 $v->{'diagnosis'} = $2;
69              
70 26 50       74 if( substr($v->{'diagnosis'}, -1, 1) eq '=' ) {
71             # Quoted printable
72 0         0 substr($v->{'diagnosis'}, -1, 1, '');
73 0         0 $qprintable = 1;
74             }
75 26         43 $recipients++;
76              
77             } elsif( $e =~ /\A\[Status: .+[<]([^ ]+[@][^ ]+)[>],/ ) {
78             # Expired
79             # [Status: Error, Address: , ResponseCode 421, , Host not reachable.]
80 5 50       23 if( $v->{'recipient'} ) {
81             # There are multiple recipient addresses in the message body.
82 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
83 0         0 $v = $dscontents->[-1];
84             }
85 5         17 $v->{'recipient'} = $1;
86 5         13 $v->{'diagnosis'} = $e;
87 5         9 $recipients++;
88              
89             } else {
90             # Continued line
91 106 50       162 next unless $qprintable;
92 0         0 $v->{'diagnosis'} .= $e;
93             }
94             }
95 26 50       103 return undef unless $recipients;
96              
97 26         54 for my $e ( @$dscontents ) {
98 31         87 $e->{'diagnosis'} =~ y/\n/ /;
99 31         172 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
100              
101 31         110 SESSION: for my $r ( keys %$messagesof ) {
102             # Verify each regular expression of session errors
103 31 100       43 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } };
  31         140  
  31         64  
104 10         23 $e->{'reason'} = $r;
105 10         24 last;
106             }
107             }
108 26         106 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
109             }
110              
111             1;
112             __END__