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   6005 use parent 'Sisimai::Lhost';
  17         49  
  17         99  
3 17     17   1038 use feature ':5.10';
  17         26  
  17         1155  
4 17     17   113 use strict;
  17         54  
  17         328  
5 17     17   90 use warnings;
  17         27  
  17         12586  
6              
7 2     2 1 1129 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 750 my $class = shift;
16 245   100     658 my $mhead = shift // return undef;
17 244   50     650 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       821 return undef unless $mhead->{'x-zohomail'};
23              
24 26         57 state $indicators = __PACKAGE__->INDICATORS;
25 26         52 state $rebackbone = qr|^Received:[ ]*from[ ]mail[.]zoho[.]com[ ]by[ ]mx[.]zohomail[.]com|m;
26 26         42 state $startingof = { 'message' => ['This message was created automatically by mail delivery'] };
27 26         59 state $messagesof = { 'expired' => ['Host not reachable'] };
28              
29 26         92 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
30 26         130 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
31 26         71 my $readcursor = 0; # (Integer) Points the current cursor position
32 26         52 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
33 26         48 my $qprintable = 0;
34 26         47 my $v = undef;
35              
36 26         166 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       358 unless( $readcursor ) {
40             # Beginning of the bounce message or message/delivery-status part
41 52 100       244 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
42 52         92 next;
43             }
44 198 50       326 next unless $readcursor & $indicators->{'deliverystatus'};
45 198 100       303 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         151 $v = $dscontents->[-1];
59              
60 137 100       470 if( $e =~ /\A([^ ]+[@][^ ]+)[ \t]+(.+)\z/ ) {
    100          
61             # kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.=
62 26 100       82 if( $v->{'recipient'} ) {
63             # There are multiple recipient addresses in the message body.
64 5         23 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
65 5         12 $v = $dscontents->[-1];
66             }
67 26         97 $v->{'recipient'} = $1;
68 26         60 $v->{'diagnosis'} = $2;
69              
70 26 50       97 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         53 $recipients++;
76              
77             } elsif( $e =~ /\A\[Status: .+[<]([^ ]+[@][^ ]+)[>],/ ) {
78             # Expired
79             # [Status: Error, Address: , ResponseCode 421, , Host not reachable.]
80 5 50       26 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         21 $v->{'recipient'} = $1;
86 5         15 $v->{'diagnosis'} = $e;
87 5         10 $recipients++;
88              
89             } else {
90             # Continued line
91 106 50       199 next unless $qprintable;
92 0         0 $v->{'diagnosis'} .= $e;
93             }
94             }
95 26 50       91 return undef unless $recipients;
96              
97 26         80 for my $e ( @$dscontents ) {
98 31         75 $e->{'diagnosis'} =~ y/\n/ /;
99 31         177 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
100              
101 31         108 SESSION: for my $r ( keys %$messagesof ) {
102             # Verify each regular expression of session errors
103 31 100       47 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } };
  31         176  
  31         76  
104 10         25 $e->{'reason'} = $r;
105 10         26 last;
106             }
107             }
108 26         145 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
109             }
110              
111             1;
112             __END__