1package Sisimai::Lhost::Activehunter;
2use parent 'Sisimai::Lhost';
3use feature ':5.10';
4use strict;
5use warnings;
6
7sub description { 'TransWARE Active!hunter' };
8sub make {
9    # Detect an error from TransWARE Active!hunter
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.1
15    my $class = shift;
16    my $mhead = shift // return undef;
17    my $mbody = shift // return undef;
18
19    # 'from'    => qr/\A"MAILER-DAEMON"/,
20    # 'subject' => qr/FAILURE NOTICE :/,
21    return undef unless defined $mhead->{'x-ahmailid'};
22
23    state $indicators = __PACKAGE__->INDICATORS;
24    state $rebackbone = qr|^Content-Type:[ ]message/rfc822|m;
25    state $startingof = { 'message' => ['  ----- The following addresses had permanent fatal errors -----'] };
26
27    my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
28    my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
29    my $readcursor = 0;     # (Integer) Points the current cursor position
30    my $recipients = 0;     # (Integer) The number of 'Final-Recipient' header
31    my $v = undef;
32
33    for my $e ( split("\n", $emailsteak->[0]) ) {
34        # Read error messages and delivery status lines from the head of the email
35        # to the previous line of the beginning of the original message.
36        unless( $readcursor ) {
37            # Beginning of the bounce message or delivery status part
38            $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
39            next;
40        }
41        next unless $readcursor & $indicators->{'deliverystatus'};
42        next unless length $e;
43
44        #  ----- The following addresses had permanent fatal errors -----
45        #
46        # >>> kijitora@example.org <kijitora@example.org>
47        #
48        #  ----- Transcript of session follows -----
49        # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr)
50        $v = $dscontents->[-1];
51
52        if( $e =~ /\A[>]{3}[ \t]+.+[<]([^ ]+?[@][^ ]+?)[>]\z/ ) {
53            # >>> kijitora@example.org <kijitora@example.org>
54            if( $v->{'recipient'} ) {
55                # There are multiple recipient addresses in the message body.
56                push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
57                $v = $dscontents->[-1];
58            }
59            $v->{'recipient'} = $1;
60            $recipients++;
61
62        } else {
63            #  ----- Transcript of session follows -----
64            # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr)
65            next unless $e =~ /\A[0-9A-Za-z]+/;
66            next if length $v->{'diagnosis'};
67            $v->{'diagnosis'} ||= $e;
68        }
69    }
70    return undef unless $recipients;
71
72    require Sisimai::String;
73    $_->{'diagnosis'} = Sisimai::String->sweep($_->{'diagnosis'}) for @$dscontents;
74    return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
75}
76
771;
78__END__
79
80=encoding utf-8
81
82=head1 NAME
83
84Sisimai::Lhost::Activehunter - bounce mail parser class for Active!hunter.
85
86=head1 SYNOPSIS
87
88    use Sisimai::Lhost::Activehunter;
89
90=head1 DESCRIPTION
91
92Sisimai::Lhost::Activehunter parses a bounce email which created by C<TransWARE
93Active!hunter>. Methods in the module are called from only Sisimai::Message.
94
95=head1 CLASS METHODS
96
97=head2 C<B<description()>>
98
99C<description()> returns description string of this module.
100
101    print Sisimai::Lhost::Activehunter->description;
102
103=head2 C<B<make(I<header data>, I<reference to body string>)>>
104
105C<make()> method parses a bounced email and return results as a array reference.
106See Sisimai::Message for more details.
107
108=head1 AUTHOR
109
110azumakuniyuki
111
112=head1 COPYRIGHT
113
114Copyright (C) 2014-2020 azumakuniyuki, All rights reserved.
115
116=head1 LICENSE
117
118This software is distributed under The BSD 2-Clause License.
119
120=cut
121
122