File Coverage

lib/Sisimai/Lhost/Zoho.pm
Criterion Covered Total %
statement 57 62 91.9
branch 19 24 79.1
condition 5 7 71.4
subroutine 6 6 100.0
pod 2 2 100.0
total 89 101 88.1


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Zoho;
2 38     38   4279 use parent 'Sisimai::Lhost';
  38         88  
  38         317  
3 38     38   3091 use v5.26;
  38         131  
4 38     38   538 use strict;
  38         82  
  38         1313  
5 38     38   177 use warnings;
  38         91  
  38         33295  
6              
7 1     1 1 5 sub description { 'Zoho Mail: https://www.zoho.com/mail/' }
8             sub inquire {
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 decode or the arguments are missing
14             # @since v4.1.7
15 915     915 1 4319 my $class = shift;
16 915 100 100     2627 my $mhead = shift // return undef; return undef unless $mhead->{'x-zohomail'};
  914         3491  
17 26   50     106 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 26         79 state $indicators = __PACKAGE__->INDICATORS;
23 26         58 state $boundaries = ['Received: from mail.zoho.com by mx.zohomail.com'];
24 26         73 state $startingof = {'message' => ['This message was created automatically by mail delivery']};
25 26         50 state $messagesof = {'expired' => ['Host not reachable']};
26              
27 26         142 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  26         62  
28 26         145 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
29 26         61 my $readcursor = 0; # (Integer) Points the current cursor position
30 26         74 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
31 26         74 my $qprintable = 0;
32              
33 26         215 for my $e ( split("\n", $emailparts->[0]) ) {
34             # Read error messages and delivery status lines from the head of the email to the previous
35             # line of the beginning of the original message.
36 219 100       432 unless( $readcursor ) {
37             # Beginning of the bounce message or message/delivery-status part
38 26 50       170 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
39 26         61 next;
40             }
41 193 100 66     797 next if ($readcursor & $indicators->{'deliverystatus'}) == 0 || $e eq "";
42              
43             # This message was created automatically by mail delivery software.
44             # A message that you sent could not be delivered to one or more of its recip=
45             # ients. This is a permanent error.=20
46             #
47             # kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.=
48             # 1 ... User Unknown
49              
50             # This message was created automatically by mail delivery software.
51             # A message that you sent could not be delivered to one or more of its recipients. This is a permanent error.
52             #
53             # shironeko@example.org Invalid Address, ERROR_CODE :550, ERROR_CODE :Requested action not taken: mailbox unavailable
54 132         219 $v = $dscontents->[-1];
55              
56 132 100       500 if( Sisimai::String->aligned(\$e, ['@', ' ', 'ERROR_CODE :']) ) {
    100          
57             # kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.=
58 26 100       129 if( $v->{'recipient'} ) {
59             # There are multiple recipient addresses in the message body.
60 5         26 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
61 5         16 $v = $dscontents->[-1];
62             }
63 26         107 $v->{'recipient'} = substr($e, 0, index($e, ' '));
64 26         97 $v->{'diagnosis'} = substr($e, index($e, ' ') + 1,);
65              
66 26 50       95 if( substr($v->{'diagnosis'}, -1, 1) eq '=' ) {
67             # Quoted printable
68 0         0 substr($v->{'diagnosis'}, -1, 1, '');
69 0         0 $qprintable = 1;
70             }
71 26         80 $recipients++;
72              
73             } elsif( index($e, '[Status: ') == 0 ) {
74             # Expired
75             # [Status: Error, Address: , ResponseCode 421, , Host not reachable.]
76 5 50       47 if( $v->{'recipient'} ) {
77             # There are multiple recipient addresses in the message body.
78 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
79 0         0 $v = $dscontents->[-1];
80             }
81 5         16 my $p1 = index($e, '<');
82 5         12 my $p2 = index($e, '>', $p1 + 2);
83 5         57 $v->{'recipient'} = Sisimai::Address->s3s4(substr($e, $p1, $p2 - $p1));
84 5         17 $v->{'diagnosis'} = $e;
85 5         18 $recipients++;
86              
87             } else {
88             # Continued line
89 101 50       279 next unless $qprintable;
90 0         0 $v->{'diagnosis'} .= $e;
91             }
92             }
93 26 50       150 return undef unless $recipients;
94              
95 26         66 for my $e ( @$dscontents ) {
96 31         97 $e->{'diagnosis'} =~ y/\n/ /;
97 31         136 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
98              
99 31         109 SESSION: for my $r ( keys %$messagesof ) {
100             # Verify each regular expression of session errors
101 31 100       103 next unless grep { index($e->{'diagnosis'}, $_) > -1 } $messagesof->{ $r }->@*;
  31         169  
102 10         21 $e->{'reason'} = $r;
103 10         22 last;
104             }
105             }
106 26         182 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
107             }
108              
109             1;
110             __END__