1package Sisimai::Lhost::Notes; 2use parent 'Sisimai::Lhost'; 3use feature ':5.10'; 4use strict; 5use warnings; 6use Encode; 7 8sub description { 'Lotus Notes' } 9sub make { 10 # Detect an error from 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 parse or the arguments are missing 15 # @since v4.1.1 16 my $class = shift; 17 my $mhead = shift // return undef; 18 my $mbody = shift // return undef; 19 return undef unless index($mhead->{'subject'}, 'Undeliverable message') == 0; 20 21 state $indicators = __PACKAGE__->INDICATORS; 22 state $rebackbone = qr|^-------[ ]Returned[ ]Message[ ]--------|m; 23 state $startingof = { 'message' => ['------- Failure Reasons '] }; 24 state $messagesof = { 25 'userunknown' => [ 26 'User not listed in public Name & Address Book', 27 'ディレクトリのリストにありません', 28 ], 29 'networkerror' => ['Message has exceeded maximum hop count'], 30 }; 31 32 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; 33 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); 34 my $readcursor = 0; # (Integer) Points the current cursor position 35 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header 36 my $removedmsg = 'MULTIBYTE CHARACTERS HAVE BEEN REMOVED'; 37 my $encodedmsg = ''; 38 my $v = undef; 39 40 # Get character set name, Content-Type: text/plain; charset=ISO-2022-JP 41 my $characters = $mhead->{'content-type'} =~ /\A.+;[ ]*charset=(.+)\z/ ? lc $1 : ''; 42 43 for my $e ( split("\n", $emailsteak->[0]) ) { 44 # Read error messages and delivery status lines from the head of the email 45 # to the previous line of the beginning of the original message. 46 unless( $readcursor ) { 47 # Beginning of the bounce message or message/delivery-status part 48 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0; 49 next; 50 } 51 next unless $readcursor & $indicators->{'deliverystatus'}; 52 53 # ------- Failure Reasons -------- 54 # 55 # User not listed in public Name & Address Book 56 # kijitora@notes.example.jp 57 # 58 # ------- Returned Message -------- 59 $v = $dscontents->[-1]; 60 if( $e =~ /\A[^ ]+[@][^ ]+/ ) { 61 # kijitora@notes.example.jp 62 if( $v->{'recipient'} ) { 63 # There are multiple recipient addresses in the message body. 64 push @$dscontents, __PACKAGE__->DELIVERYSTATUS; 65 $v = $dscontents->[-1]; 66 } 67 $v->{'recipient'} ||= $e; 68 $recipients++; 69 70 } else { 71 next if $e eq ''; 72 next if index($e, '-') == 0; 73 74 if( $e =~ /[^\x20-\x7e]/ ) { 75 # Error message is not ISO-8859-1 76 $encodedmsg = $e; 77 if( $characters ) { 78 # Try to convert string 79 eval { Encode::from_to($encodedmsg, $characters, 'utf8'); }; 80 $encodedmsg = $removedmsg if $@; # Failed to convert 81 82 } else { 83 # No character set in Content-Type header 84 $encodedmsg = $removedmsg; 85 } 86 $v->{'diagnosis'} .= $encodedmsg; 87 88 } else { 89 # Error message does not include multi-byte character 90 $v->{'diagnosis'} .= $e; 91 } 92 } 93 } 94 95 unless( $recipients ) { 96 # Fallback: Get the recpient address from RFC822 part 97 if( $emailsteak->[1] =~ /^To:[ ]*(.+)$/m ) { 98 $v->{'recipient'} = Sisimai::Address->s3s4($1); 99 $recipients++ if $v->{'recipient'}; 100 } 101 } 102 return undef unless $recipients; 103 104 for my $e ( @$dscontents ) { 105 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'}); 106 $e->{'recipient'} = Sisimai::Address->s3s4($e->{'recipient'}); 107 108 for my $r ( keys %$messagesof ) { 109 # Check each regular expression of Notes error messages 110 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } }; 111 $e->{'reason'} = $r; 112 $e->{'status'} = Sisimai::SMTP::Status->code($r) || ''; 113 last; 114 } 115 } 116 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; 117} 118 1191; 120__END__ 121 122=encoding utf-8 123 124=head1 NAME 125 126Sisimai::Lhost::Notes - bounce mail parser class for C<Lotus Notes Server>. 127 128=head1 SYNOPSIS 129 130 use Sisimai::Lhost::Notes; 131 132=head1 DESCRIPTION 133 134Sisimai::Lhost::Notes parses a bounce email which created by 135C<Lotus Notes Server>. 136Methods in the module are called from only Sisimai::Message. 137 138=head1 CLASS METHODS 139 140=head2 C<B<description()>> 141 142C<description()> returns description string of this module. 143 144 print Sisimai::Lhost::Notes->description; 145 146=head2 C<B<make(I<header data>, I<reference to body string>)>> 147 148C<make()> method parses a bounced email and return results as a array reference. 149See Sisimai::Message for more details. 150 151=head1 AUTHOR 152 153azumakuniyuki 154 155=head1 COPYRIGHT 156 157Copyright (C) 2014-2020 azumakuniyuki, All rights reserved. 158 159=head1 LICENSE 160 161This software is distributed under The BSD 2-Clause License. 162 163=cut 164 165