File Coverage

lib/Sisimai/Lhost/Bigfoot.pm
Criterion Covered Total %
statement 69 75 92.0
branch 32 44 72.7
condition 10 23 43.4
subroutine 6 6 100.0
pod 2 2 100.0
total 119 150 79.3


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Bigfoot;
2 17     17   5578 use parent 'Sisimai::Lhost';
  17         28  
  17         84  
3 17     17   940 use feature ':5.10';
  17         27  
  17         1058  
4 17     17   79 use strict;
  17         49  
  17         332  
5 17     17   74 use warnings;
  17         29  
  17         13710  
6              
7 2     2 1 1717 sub description { 'Bigfoot: http://www.bigfoot.com' }
8             sub make {
9             # Detect an error from Bigfoot
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.10
15 210     210 1 734 my $class = shift;
16 210   100     467 my $mhead = shift // return undef;
17 209   50     448 my $mbody = shift // return undef;
18 209         261 my $match = 0;
19              
20             # 'subject' => qr/\AReturned mail: /,
21 209 100 50     622 $match ||= 1 if rindex($mhead->{'from'}, '@bigfoot.com>') > -1;
22 209 100 50     274 $match ||= 1 if grep { rindex($_, '.bigfoot.com ') > -1 } @{ $mhead->{'received'} };
  347         886  
  209         518  
23 209 100       569 return undef unless $match;
24              
25 6         44 state $indicators = __PACKAGE__->INDICATORS;
26 6         13 state $rebackbone = qr|^Content-Type:[ ]message/partial|m;
27 6         21 state $markingsof = { 'message' => qr/\A[ \t]+[-]+[ \t]*Transcript of session follows/ };
28              
29 6         366 require Sisimai::RFC1894;
30 6         31 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
31 6         13 my $permessage = {}; # (Hash) Store values of each Per-Message field
32              
33 6         34 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
34 6         19 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
35 6         21 my $readcursor = 0; # (Integer) Points the current cursor position
36 6         10 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
37 6         7 my $commandtxt = ''; # (String) SMTP Command name begin with the string '>>>'
38 6         15 my $esmtpreply = ''; # (String) Reply from remote server on SMTP session
39 6         11 my $v = undef;
40 6         8 my $p = '';
41              
42 6         48 for my $e ( split("\n", $emailsteak->[0]) ) {
43             # Read error messages and delivery status lines from the head of the email
44             # to the previous line of the beginning of the original message.
45 132 100       147 unless( $readcursor ) {
46             # Beginning of the bounce message or message/delivery-status part
47 54 100       143 $readcursor |= $indicators->{'deliverystatus'} if $e =~ $markingsof->{'message'};
48 54         55 next;
49             }
50 78 50       106 next unless $readcursor & $indicators->{'deliverystatus'};
51 78 100       105 next unless length $e;
52              
53 66 100       108 if( my $f = Sisimai::RFC1894->match($e) ) {
54             # $e matched with any field defined in RFC3464
55 48 50       92 next unless my $o = Sisimai::RFC1894->field($e);
56 48         55 $v = $dscontents->[-1];
57              
58 48 100       80 if( $o->[-1] eq 'addr' ) {
    100          
59             # Final-Recipient: rfc822; kijitora@example.jp
60             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
61 6 50       25 if( $o->[0] eq 'final-recipient' ) {
62             # Final-Recipient: rfc822; kijitora@example.jp
63 6 50       30 if( $v->{'recipient'} ) {
64             # There are multiple recipient addresses in the message body.
65 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
66 0         0 $v = $dscontents->[-1];
67             }
68 6         12 $v->{'recipient'} = $o->[2];
69 6         13 $recipients++;
70              
71             } else {
72             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
73 0         0 $v->{'alias'} = $o->[2];
74             }
75             } elsif( $o->[-1] eq 'code' ) {
76             # Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
77 6         23 $v->{'spec'} = $o->[1];
78 6         14 $v->{'diagnosis'} = $o->[2];
79              
80             } else {
81             # Other DSN fields defined in RFC3464
82 36 50       61 next unless exists $fieldtable->{ $o->[0] };
83 36         53 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
84              
85 36 100       65 next unless $f == 1;
86 12         35 $permessage->{ $fieldtable->{ $o->[0] } } = $o->[2];
87             }
88             } else {
89             # The line does not begin with a DSN field defined in RFC3464
90 18 50       47 if( substr($e, 0, 1) ne ' ' ) {
91             # ----- Transcript of session follows -----
92             # >>> RCPT TO:
93             # <<< 553 Invalid recipient destinaion@example.net (Mode: normal)
94 18 100       72 if( $e =~ /\A[>]{3}[ ]+([A-Z]{4})[ ]?/ ) {
    100          
95             # >>> DATA
96 6         16 $commandtxt = $1;
97              
98             } elsif( $e =~ /\A[<]{3}[ ]+(.+)\z/ ) {
99             # <<< Response
100 6         13 $esmtpreply = $1;
101             }
102             } else {
103             # Continued line of the value of Diagnostic-Code field
104 0 0       0 next unless index($p, 'Diagnostic-Code:') == 0;
105 0 0       0 next unless $e =~ /\A[ \t]+(.+)\z/;
106 0         0 $v->{'diagnosis'} .= ' '.$1;
107             }
108             }
109             } continue {
110             # Save the current line for the next loop
111 132         164 $p = $e;
112             }
113 6 50       25 return undef unless $recipients;
114              
115 6         14 for my $e ( @$dscontents ) {
116             # Set default values if each value is empty.
117 6   33     25 $e->{'lhost'} ||= $permessage->{'rhost'};
118 6   0     38 $e->{ $_ } ||= $permessage->{ $_ } || '' for keys %$permessage;
      33        
119              
120 6         47 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
121 6   50     40 $e->{'command'} ||= $commandtxt || '';
      33        
122 6 50 50     23 $e->{'command'} ||= 'EHLO' if $esmtpreply;
123             }
124 6         38 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
125             }
126              
127             1;
128             __END__