File Coverage

lib/Sisimai/Lhost/Notes.pm
Criterion Covered Total %
statement 65 73 89.0
branch 22 34 64.7
condition 10 15 66.6
subroutine 7 7 100.0
pod 2 2 100.0
total 106 131 80.9


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Notes;
2 36     36   6093 use parent 'Sisimai::Lhost';
  36         80  
  36         252  
3 36     36   3322 use v5.26;
  36         145  
4 36     36   220 use strict;
  36         471  
  36         1168  
5 36     36   302 use warnings;
  36         300  
  36         2236  
6 36     36   251 use Encode;
  36         123  
  36         37582  
7              
8 1     1 1 5 sub description { 'HCL Notes' }
9             sub inquire {
10             # Detect an error from HCL Notes (Formerly IBM Notes(Formerly Lotus Notes))
11             # @param [Hash] mhead Message headers of a bounce email
12             # @param [String] mbody Message body of a bounce email
13             # @return [Hash] Bounce data list and message/rfc822 part
14             # @return [undef] failed to decode or the arguments are missing
15             # @since v4.1.1
16 889     889 1 4930 my $class = shift;
17 889   100     2942 my $mhead = shift // return undef;
18 888   100     2318 my $mbody = shift // return undef;
19              
20 887 100       3256 return undef unless $mhead->{'subject'};
21 870 100       3548 return undef unless index($mhead->{'subject'}, 'Undeliverable message') == 0;
22              
23 16         72 state $indicators = __PACKAGE__->INDICATORS;
24 16         30 state $boundaries = ['------- Returned Message --------'];
25 16         63 state $startingof = {'message' => ['------- Failure Reasons ']};
26 16         64 state $messagesof = {
27             'userunknown' => [
28             'User not listed in public Name & Address Book',
29             'ディレクトリのリストにありません',
30             ],
31             'networkerror' => ['Message has exceeded maximum hop count'],
32             };
33              
34 16         111 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  16         30  
35 16         70 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
36 16         33 my $readcursor = 0; # (Integer) Points the current cursor position
37 16         26 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
38 16         28 my $removedmsg = 'MULTIBYTE CHARACTERS HAVE BEEN REMOVED';
39 16         32 my $encodedmsg = '';
40              
41 16         69 my $characters = '';
42 16 50       69 if( index($mhead->{'content-type'}, 'charset=') > 0 ) {
43             # Get character set name, Content-Type: text/plain; charset=ISO-2022-JP
44 16         81 $characters = lc substr($mhead->{'content-type'}, index($mhead->{'content-type'}, 'charset=') + 8,);
45             }
46              
47 16         82 for my $e ( split("\n", $emailparts->[0]) ) {
48             # Read error messages and delivery status lines from the head of the email to the previous
49             # line of the beginning of the original message.
50 64 100       110 unless( $readcursor ) {
51             # Beginning of the bounce message or message/delivery-status part
52 16 50       111 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
53 16         34 next;
54             }
55 48 50       110 next if ($readcursor & $indicators->{'deliverystatus'}) == 0;
56              
57             # ------- Failure Reasons --------
58             #
59             # User not listed in public Name & Address Book
60             # kijitora@notes.example.jp
61             #
62             # ------- Returned Message --------
63 48         66 $v = $dscontents->[-1];
64 48 100 66     197 if( index($e, '@') > 1 && index($e, ' ') < 0 ) {
65             # kijitora@notes.example.jp
66 16 50       45 if( $v->{'recipient'} ) {
67             # There are multiple recipient addresses in the message body.
68 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
69 0         0 $v = $dscontents->[-1];
70             }
71 16   33     78 $v->{'recipient'} ||= $e;
72 16         34 $recipients++;
73              
74             } else {
75 32 100 66     114 next if $e eq '' || index($e, '-') == 0;
76              
77 16 100       121 if( $e =~ /[^\x20-\x7e]/ ) {
78             # Error message is not ISO-8859-1
79 11         20 $encodedmsg = $e;
80 11 50       27 if( $characters ) {
81             # Try to convert string
82 11         23 eval { Encode::from_to($encodedmsg, $characters, 'utf8'); };
  11         161  
83 11 50       1683 $encodedmsg = $removedmsg if $@; # Failed to convert
84              
85             } else {
86             # No character set in Content-Type header
87 0         0 $encodedmsg = $removedmsg;
88             }
89 11         45 $v->{'diagnosis'} .= $encodedmsg;
90              
91             } else {
92             # Error message does not include multi-byte character
93 5         22 $v->{'diagnosis'} .= $e;
94             }
95             }
96             }
97              
98 16 50       73 unless( $recipients ) {
99             # Fallback: Get the recpient address from RFC822 part
100 0         0 my $p1 = index($emailparts->[1], "\nTo: ");
101 0         0 my $p2 = index($emailparts->[1], "\n", $p1 + 6);
102 0 0       0 if( $p1 > 0 ) {
103 0         0 $v->{'recipient'} = Sisimai::Address->s3s4(substr($emailparts->[1], $p1 + 5, $p2 - $p1 - 5));
104 0 0       0 $recipients++ if $v->{'recipient'};
105             }
106             }
107 16 50       57 return undef unless $recipients;
108              
109 16         33 for my $e ( @$dscontents ) {
110 16         125 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
111 16         104 $e->{'recipient'} = Sisimai::Address->s3s4($e->{'recipient'});
112              
113 16         59 for my $r ( keys %$messagesof ) {
114             # Check each regular expression of Notes error messages
115 32 100       93 next unless grep { index($e->{'diagnosis'}, $_) > -1 } $messagesof->{ $r }->@*;
  48         155  
116 10         28 $e->{'reason'} = $r;
117 10   50     86 $e->{'status'} = Sisimai::SMTP::Status->code($r) || '';
118 10         29 last;
119             }
120             }
121 16         94 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
122             }
123              
124             1;
125             __END__