File Coverage

lib/Sisimai/Lhost/ReceivingSES.pm
Criterion Covered Total %
statement 71 75 94.6
branch 30 38 78.9
condition 11 20 55.0
subroutine 6 6 100.0
pod 2 2 100.0
total 120 141 85.1


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::ReceivingSES;
2 17     17   5413 use parent 'Sisimai::Lhost';
  17         43  
  17         91  
3 17     17   1018 use feature ':5.10';
  17         46  
  17         1148  
4 17     17   104 use strict;
  17         34  
  17         339  
5 17     17   79 use warnings;
  17         36  
  17         16112  
6              
7             # https://aws.amazon.com/ses/
8 2     2 1 955 sub description { 'Amazon SES(Receiving): https://aws.amazon.com/ses/' };
9             sub make {
10             # Detect an error from Amazon SES/Receiving
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.29
16 325     325 1 950 my $class = shift;
17 325   100     972 my $mhead = shift // return undef;
18 324   50     893 my $mbody = shift // return undef;
19              
20             # X-SES-Outgoing: 2015.10.01-54.240.27.7
21             # Feedback-ID: 1.us-west-2.HX6/J9OVlHTadQhEu1+wdF9DBj6n6Pa9sW5Y/0pSOi8=:AmazonSES
22 324 100       1952 return undef unless $mhead->{'x-ses-outgoing'};
23              
24 47         138 state $indicators = __PACKAGE__->INDICATORS;
25 47         105 state $rebackbone = qr|^content-type:[ ]text/rfc822-headers|m;
26 47         115 state $startingof = { 'message' => ['This message could not be delivered.'] };
27 47         107 state $messagesof = {
28             # The followings are error messages in Rule sets/*/Actions/Template
29             'filtered' => ['Mailbox does not exist'],
30             'mesgtoobig' => ['Message too large'],
31             'mailboxfull' => ['Mailbox full'],
32             'contenterror' => ['Message content rejected'],
33             };
34              
35 47         192 require Sisimai::RFC1894;
36 47         169 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
37 47         132 my $permessage = {}; # (Hash) Store values of each Per-Message field
38              
39 47         191 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
40 47         188 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
41 47         102 my $readcursor = 0; # (Integer) Points the current cursor position
42 47         104 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
43 47         94 my $v = undef;
44 47         99 my $p = '';
45              
46 47         779 for my $e ( split("\n", $emailsteak->[0]) ) {
47             # Read error messages and delivery status lines from the head of the email
48             # to the previous line of the beginning of the original message.
49 2134 100       2810 unless( $readcursor ) {
50             # Beginning of the bounce message or message/delivery-status part
51 910 100       1639 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
52 910         1312 next;
53             }
54 1224 50       1818 next unless $readcursor & $indicators->{'deliverystatus'};
55 1224 100       1734 next unless length $e;
56              
57 1080 100       1786 if( my $f = Sisimai::RFC1894->match($e) ) {
58             # $e matched with any field defined in RFC3464
59 252 50       495 next unless my $o = Sisimai::RFC1894->field($e);
60 252         370 $v = $dscontents->[-1];
61              
62 252 100       527 if( $o->[-1] eq 'addr' ) {
    100          
63             # Final-Recipient: rfc822; kijitora@example.jp
64             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
65 72 100       189 if( $o->[0] eq 'final-recipient' ) {
66             # Final-Recipient: rfc822; kijitora@example.jp
67 36 50       137 if( $v->{'recipient'} ) {
68             # There are multiple recipient addresses in the message body.
69 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
70 0         0 $v = $dscontents->[-1];
71             }
72 36         80 $v->{'recipient'} = $o->[2];
73 36         96 $recipients++;
74              
75             } else {
76             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
77 36         139 $v->{'alias'} = $o->[2];
78             }
79             } elsif( $o->[-1] eq 'code' ) {
80             # Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
81 36         92 $v->{'spec'} = $o->[1];
82 36         103 $v->{'diagnosis'} = $o->[2];
83              
84             } else {
85             # Other DSN fields defined in RFC3464
86 144 50       293 next unless exists $fieldtable->{ $o->[0] };
87 144         294 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
88              
89 144 100       334 next unless $f == 1;
90 72         216 $permessage->{ $fieldtable->{ $o->[0] } } = $o->[2];
91             }
92             } else {
93             # Continued line of the value of Diagnostic-Code field
94 828 50       1534 next unless index($p, 'Diagnostic-Code:') == 0;
95 0 0       0 next unless $e =~ /\A[ \t]+(.+)\z/;
96 0         0 $v->{'diagnosis'} .= ' '.$1;
97             }
98             } continue {
99             # Save the current line for the next loop
100 2134         3321 $p = $e;
101             }
102 47 100       319 return undef unless $recipients;
103              
104 36         95 for my $e ( @$dscontents ) {
105             # Set default values if each value is empty.
106 36   33     237 $e->{'lhost'} ||= $permessage->{'rhost'};
107 36   0     257 $e->{ $_ } ||= $permessage->{ $_ } || '' for keys %$permessage;
      33        
108 36         111 $e->{'diagnosis'} =~ y/\n/ /;
109 36         245 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
110              
111 36 100       223 if( $e->{'status'} =~ /\A[45][.][01][.]0\z/ ) {
112             # Get other D.S.N. value from the error message
113             # 5.1.0 - Unknown address error 550-'5.7.1 ...
114 5         16 my $errormessage = $e->{'diagnosis'};
115 5 50       28 $errormessage = $1 if $e->{'diagnosis'} =~ /["'](\d[.]\d[.]\d.+)['"]/;
116 5   33     37 $e->{'status'} = Sisimai::SMTP::Status->find($errormessage) || $e->{'status'};
117             }
118              
119 36         131 SESSION: for my $r ( keys %$messagesof ) {
120             # Verify each regular expression of session errors
121 111 100       196 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } };
  111         389  
  111         196  
122 26         64 $e->{'reason'} = $r;
123 26         51 last;
124             }
125 36   100     180 $e->{'reason'} ||= Sisimai::SMTP::Status->name($e->{'status'}) || '';
      100        
126             }
127 36         278 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
128             }
129              
130             1;
131             __END__