| 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__ |