File Coverage

lib/Sisimai/Lhost/Yandex.pm
Criterion Covered Total %
statement 68 69 98.5
branch 31 38 81.5
condition 9 14 64.2
subroutine 6 6 100.0
pod 2 2 100.0
total 116 129 89.9


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Yandex;
2 25     25   6231 use parent 'Sisimai::Lhost';
  25         60  
  25         152  
3 25     25   1669 use feature ':5.10';
  25         62  
  25         1826  
4 25     25   151 use strict;
  25         39  
  25         531  
5 25     25   116 use warnings;
  25         61  
  25         22986  
6              
7 2     2 1 1314 sub description { 'Yandex.Mail: https://www.yandex.ru' }
8             sub make {
9             # Detect an error from Yandex.Mail
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.6
15 277     277 1 848 my $class = shift;
16 277   100     1084 my $mhead = shift // return undef;
17 276   50     661 my $mbody = shift // return undef;
18              
19             # X-Yandex-Front: mxback1h.mail.yandex.net
20             # X-Yandex-TimeMark: 1417885948
21             # X-Yandex-Uniq: 92309766-f1c8-4bd4-92bc-657c75766587
22             # X-Yandex-Spam: 1
23             # X-Yandex-Forward: 10104c00ad0726da5f37374723b1e0c8
24             # X-Yandex-Queue-ID: 367D79E130D
25             # X-Yandex-Sender: rfc822; shironeko@yandex.example.com
26 276 100       859 return undef unless $mhead->{'x-yandex-uniq'};
27 16 50       97 return undef unless $mhead->{'from'} eq 'mailer-daemon@yandex.ru';
28              
29 16         53 state $indicators = __PACKAGE__->INDICATORS;
30 16         50 state $rebackbone = qr|^Content-Type:[ ]message/rfc822|m;
31 16         37 state $startingof = { 'message' => ['This is the mail system at host yandex.ru.'] };
32              
33 16         408 require Sisimai::RFC1894;
34 16         81 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
35 16         48 my $permessage = {}; # (Hash) Store values of each Per-Message field
36              
37 16         68 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
38 16         95 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
39 16         72 my $readcursor = 0; # (Integer) Points the current cursor position
40 16         44 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
41 16         36 my @commandset; # (Array) ``in reply to * command'' list
42 16         23 my $v = undef;
43 16         34 my $p = '';
44              
45 16         256 for my $e ( split("\n", $emailsteak->[0]) ) {
46             # Read error messages and delivery status lines from the head of the email
47             # to the previous line of the beginning of the original message.
48 621 100       867 unless( $readcursor ) {
49             # Beginning of the bounce message or message/delivery-status part
50 224 100       485 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
51 224         238 next;
52             }
53 397 50       654 next unless $readcursor & $indicators->{'deliverystatus'};
54 397 100       545 next unless length $e;
55              
56 307 100       578 if( my $f = Sisimai::RFC1894->match($e) ) {
57             # $e matched with any field defined in RFC3464
58 153 50       283 next unless my $o = Sisimai::RFC1894->field($e);
59 153         229 $v = $dscontents->[-1];
60              
61 153 100       432 if( $o->[-1] eq 'addr' ) {
    100          
62             # Final-Recipient: rfc822; kijitora@example.jp
63             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
64 42 100       145 if( $o->[0] eq 'final-recipient' ) {
65             # Final-Recipient: rfc822; kijitora@example.jp
66 21 100       106 if( $v->{'recipient'} ) {
67             # There are multiple recipient addresses in the message body.
68 5         65 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
69 5         18 $v = $dscontents->[-1];
70             }
71 21         49 $v->{'recipient'} = $o->[2];
72 21         41 $recipients++;
73              
74             } else {
75             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
76 21         75 $v->{'alias'} = $o->[2];
77             }
78             } elsif( $o->[-1] eq 'code' ) {
79             # Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
80 21         86 $v->{'spec'} = $o->[1];
81 21         74 $v->{'diagnosis'} = $o->[2];
82              
83             } else {
84             # Other DSN fields defined in RFC3464
85 90 50       188 next unless exists $fieldtable->{ $o->[0] };
86 90         189 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
87              
88 90 100       185 next unless $f == 1;
89 32         94 $permessage->{ $fieldtable->{ $o->[0] } } = $o->[2];
90             }
91             } else {
92             # The line does not begin with a DSN field defined in RFC3464
93             # : host mx.example.jp[192.0.2.153] said: 550
94             # 5.1.1 ... User Unknown (in reply to RCPT TO
95             # command)
96 154 100       464 if( $e =~ /[ \t][(]in reply to .*([A-Z]{4}).*/ ) {
    50          
97             # 5.1.1 ... User Unknown (in reply to RCPT TO
98 16         75 push @commandset, $1;
99              
100             } elsif( $e =~ /([A-Z]{4})[ \t]*.*command[)]\z/ ) {
101             # to MAIL command)
102 0         0 push @commandset, $1;
103              
104             } else {
105             # Continued line of the value of Diagnostic-Code field
106 138 100       292 next unless index($p, 'Diagnostic-Code:') == 0;
107 5 50       53 next unless $e =~ /\A[ \t]+(.+)\z/;
108 5         21 $v->{'diagnosis'} .= ' '.$1;
109             }
110             }
111             } continue {
112             # Save the current line for the next loop
113 621         876 $p = $e;
114             }
115 16 50       130 return undef unless $recipients;
116              
117 16         57 for my $e ( @$dscontents ) {
118             # Set default values if each value is empty.
119 21   33     170 $e->{'lhost'} ||= $permessage->{'rhost'};
120 21   50     159 $e->{ $_ } ||= $permessage->{ $_ } || '' for keys %$permessage;
      66        
121 21   100     92 $e->{'command'} = shift @commandset || '';
122 21         53 $e->{'diagnosis'} =~ y/\n/ /;
123 21         140 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
124             }
125 16         197 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
126             }
127              
128             1;
129             __END__