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 37     37   4616 use parent 'Sisimai::Lhost';
  37         59  
  37         346  
3 37     37   2470 use v5.26;
  37         98  
4 37     37   351 use strict;
  37         175  
  37         764  
5 37     37   127 use warnings;
  37         47  
  37         1830  
6 37     37   331 use Encode;
  37         320  
  37         27673  
7              
8 1     1 1 2 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 887     887 1 2738 my $class = shift;
17 887   100     1853 my $mhead = shift // return undef;
18 886   100     1690 my $mbody = shift // return undef;
19              
20 885 100       2037 return undef unless $mhead->{'subject'};
21 867 100       2572 return undef unless index($mhead->{'subject'}, 'Undeliverable message') == 0;
22              
23 16         37 state $indicators = __PACKAGE__->INDICATORS;
24 16         21 state $boundaries = ['------- Returned Message --------'];
25 16         29 state $startingof = {'message' => ['------- Failure Reasons ']};
26 16         26 state $messagesof = {
27             'userunknown' => [
28             'User not listed in public Name & Address Book',
29             'ディレクトリのリストにありません',
30             ],
31             };
32              
33 16         60 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  16         24  
34 16         74 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
35 16         17 my $readcursor = 0; # (Integer) Points the current cursor position
36 16         23 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
37 16         18 my $removedmsg = 'MULTIBYTE CHARACTERS HAVE BEEN REMOVED';
38 16         19 my $encodedmsg = '';
39              
40 16         14 my $characters = '';
41 16 50       40 if( index($mhead->{'content-type'}, 'charset=') > 0 ) {
42             # Get character set name, Content-Type: text/plain; charset=ISO-2022-JP
43 16         42 $characters = lc substr($mhead->{'content-type'}, index($mhead->{'content-type'}, 'charset=') + 8,);
44             }
45              
46 16         56 for my $e ( split("\n", $emailparts->[0]) ) {
47             # Read error messages and delivery status lines from the head of the email to the previous
48             # line of the beginning of the original message.
49 64 100       92 unless( $readcursor ) {
50             # Beginning of the bounce message or message/delivery-status part
51 16 50       60 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
52 16         19 next;
53             }
54 48 50       68 next if ($readcursor & $indicators->{'deliverystatus'}) == 0;
55              
56             # ------- Failure Reasons --------
57             #
58             # User not listed in public Name & Address Book
59             # kijitora@notes.example.jp
60             #
61             # ------- Returned Message --------
62 48         43 $v = $dscontents->[-1];
63 48 100 66     124 if( index($e, '@') > 1 && index($e, ' ') < 0 ) {
64             # kijitora@notes.example.jp
65 16 50       45 if( $v->{'recipient'} ) {
66             # There are multiple recipient addresses in the message body.
67 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
68 0         0 $v = $dscontents->[-1];
69             }
70 16   33     63 $v->{'recipient'} ||= $e;
71 16         24 $recipients++;
72              
73             } else {
74 32 100 66     102 next if $e eq '' || index($e, '-') == 0;
75              
76 16 100       53 if( $e =~ /[^\x20-\x7e]/ ) {
77             # Error message is not ISO-8859-1
78 11         12 $encodedmsg = $e;
79 11 50       20 if( $characters ) {
80             # Try to convert string
81 11         17 eval { Encode::from_to($encodedmsg, $characters, 'utf8'); };
  11         109  
82 11 50       1247 $encodedmsg = $removedmsg if $@; # Failed to convert
83              
84             } else {
85             # No character set in Content-Type header
86 0         0 $encodedmsg = $removedmsg;
87             }
88 11         30 $v->{'diagnosis'} .= $encodedmsg;
89              
90             } else {
91             # Error message does not include multi-byte character
92 5         17 $v->{'diagnosis'} .= $e;
93             }
94             }
95             }
96              
97 16 50       62 unless( $recipients ) {
98             # Fallback: Get the recpient address from RFC822 part
99 0         0 my $p1 = index($emailparts->[1], "\nTo: ");
100 0         0 my $p2 = index($emailparts->[1], "\n", $p1 + 6);
101 0 0       0 if( $p1 > 0 ) {
102 0         0 $v->{'recipient'} = Sisimai::Address->s3s4(substr($emailparts->[1], $p1 + 5, $p2 - $p1 - 5));
103 0 0       0 $recipients++ if $v->{'recipient'};
104             }
105             }
106 16 50       36 return undef unless $recipients;
107              
108 16         29 for my $e ( @$dscontents ) {
109 16         91 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
110 16         80 $e->{'recipient'} = Sisimai::Address->s3s4($e->{'recipient'});
111              
112 16         37 for my $r ( keys %$messagesof ) {
113             # Check each regular expression of Notes error messages
114 16 100       36 next unless grep { index($e->{'diagnosis'}, $_) > -1 } $messagesof->{ $r }->@*;
  32         89  
115 10         18 $e->{'reason'} = $r;
116 10   50     73 $e->{'status'} = Sisimai::SMTP::Status->code($r) || '';
117 10         39 last;
118             }
119             }
120 16         71 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
121             }
122              
123             1;
124             __END__