1package Sisimai::Lhost::ApacheJames;
2use parent 'Sisimai::Lhost';
3use feature ':5.10';
4use strict;
5use warnings;
6
7sub description { 'Java Apache Mail Enterprise Server' }
8sub make {
9    # Detect an error from ApacheJames
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.26
15    my $class = shift;
16    my $mhead = shift // return undef;
17    my $mbody = shift // return undef;
18    my $match = 0;
19
20    # 'subject'    => qr/\A\[BOUNCE\]\z/,
21    # 'received'   => qr/JAMES SMTP Server/,
22    # 'message-id' => qr/\d+[.]JavaMail[.].+[@]/,
23    $match ||= 1 if $mhead->{'subject'} eq '[BOUNCE]';
24    $match ||= 1 if defined $mhead->{'message-id'} && rindex($mhead->{'message-id'}, '.JavaMail.') > -1;
25    $match ||= 1 if grep { rindex($_, 'JAMES SMTP Server') > -1 } @{ $mhead->{'received'} };
26    return undef unless $match;
27
28    state $indicators = __PACKAGE__->INDICATORS;
29    state $rebackbone = qr|^Content-Type:[ ]message/rfc822|m;
30    state $startingof = {
31        # apache-james-2.3.2/src/java/org/apache/james/transport/mailets/
32        #   AbstractNotify.java|124:  out.println("Error message below:");
33        #   AbstractNotify.java|128:  out.println("Message details:");
34        'message' => [''],
35        'error'   => ['Error message below:'],
36    };
37
38    my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
39    my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
40    my $readcursor = 0;     # (Integer) Points the current cursor position
41    my $recipients = 0;     # (Integer) The number of 'Final-Recipient' header
42    my $diagnostic = '';    # (String) Alternative diagnostic message
43    my $subjecttxt = undef; # (String) Alternative Subject text
44    my $gotmessage = 0;     # (Integer) Flag for error message
45    my $v = undef;
46
47    for my $e ( split("\n", $emailsteak->[0]) ) {
48        # Read error messages and delivery status lines from the head of the email
49        # to the previous line of the beginning of the original message.
50        unless( $readcursor ) {
51            # Beginning of the bounce message or message/delivery-status part
52            $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
53            next;
54        }
55        next unless $readcursor & $indicators->{'deliverystatus'};
56        next unless length $e;
57
58        # Message details:
59        #   Subject: Nyaaan
60        #   Sent date: Thu Apr 29 01:20:50 JST 2015
61        #   MAIL FROM: shironeko@example.jp
62        #   RCPT TO: kijitora@example.org
63        #   From: Neko <shironeko@example.jp>
64        #   To: kijitora@example.org
65        #   Size (in bytes): 1024
66        #   Number of lines: 64
67        $v = $dscontents->[-1];
68
69        if( $e =~ /\A[ ][ ]RCPT[ ]TO:[ ]([^ ]+[@][^ ]+)\z/ ) {
70            #   RCPT TO: kijitora@example.org
71            if( $v->{'recipient'} ) {
72                # There are multiple recipient addresses in the message body.
73                push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
74                $v = $dscontents->[-1];
75            }
76            $v->{'recipient'} = $1;
77            $recipients++;
78
79        } elsif( $e =~ /\A[ ][ ]Sent[ ]date:[ ](.+)\z/ ) {
80            #   Sent date: Thu Apr 29 01:20:50 JST 2015
81            $v->{'date'} = $1;
82
83        } elsif( $e =~ /\A[ ][ ]Subject:[ ](.+)\z/ ) {
84            #   Subject: Nyaaan
85            $subjecttxt = $1;
86
87        } else {
88            next if $gotmessage == 1;
89
90            if( $v->{'diagnosis'} ) {
91                # Get an error message text
92                if( $e eq 'Message details:' ) {
93                    # Message details:
94                    #   Subject: nyaan
95                    #   ...
96                    $gotmessage = 1;
97
98                } else {
99                    # Append error message text like the followng:
100                    #   Error message below:
101                    #   550 - Requested action not taken: no such user here
102                    $v->{'diagnosis'} .= ' '.$e;
103                }
104            } else {
105                # Error message below:
106                # 550 - Requested action not taken: no such user here
107                $v->{'diagnosis'} = $e if $e eq $startingof->{'error'}->[0];
108                $v->{'diagnosis'} .= ' '.$e unless $gotmessage;
109            }
110        }
111    }
112    return undef unless $recipients;
113
114    # Set the value of $subjecttxt as a Subject if there is no original message
115    # in the bounce mail.
116    $emailsteak->[1] .= sprintf("Subject: %s\n", $subjecttxt) unless $emailsteak->[1] =~ /^Subject:/m;
117    $_->{'diagnosis'} = Sisimai::String->sweep($_->{'diagnosis'} || $diagnostic) for @$dscontents;
118    return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
119}
120
1211;
122__END__
123
124=encoding utf-8
125
126=head1 NAME
127
128Sisimai::Lhost::ApacheJames - bounce mail parser class for C<ApacheJames>.
129
130=head1 SYNOPSIS
131
132    use Sisimai::Lhost::ApacheJames;
133
134=head1 DESCRIPTION
135
136Sisimai::Lhost::ApacheJames parses a bounce email which created by C<ApacheJames>.
137Methods in the module are called from only Sisimai::Message.
138
139=head1 CLASS METHODS
140
141=head2 C<B<description()>>
142
143C<description()> returns description string of this module.
144
145    print Sisimai::Lhost::ApacheJames->description;
146
147=head2 C<B<make(I<header data>, I<reference to body string>)>>
148
149C<make()> method parses a bounced email and return results as a array reference.
150See Sisimai::Message for more details.
151
152=head1 AUTHOR
153
154azumakuniyuki
155
156=head1 COPYRIGHT
157
158Copyright (C) 2015-2020 azumakuniyuki, All rights reserved.
159
160=head1 LICENSE
161
162This software is distributed under The BSD 2-Clause License.
163
164=cut
165
166