File Coverage

lib/Sisimai/Lhost/MessageLabs.pm
Criterion Covered Total %
statement 65 69 94.2
branch 29 38 76.3
condition 5 12 41.6
subroutine 6 6 100.0
pod 2 2 100.0
total 107 127 84.2


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::MessageLabs;
2 16     16   6129 use parent 'Sisimai::Lhost';
  16         32  
  16         96  
3 16     16   979 use feature ':5.10';
  16         29  
  16         1050  
4 16     16   101 use strict;
  16         43  
  16         350  
5 16     16   74 use warnings;
  16         30  
  16         14443  
6              
7 2     2 1 1189 sub description { 'Symantec.cloud http://www.messagelabs.com' }
8             sub make {
9             # Detect an error from MessageLabs.com
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 219     219 1 776 my $class = shift;
16 219   100     827 my $mhead = shift // return undef;
17 218   50     586 my $mbody = shift // return undef;
18              
19             # X-Msg-Ref: server-11.tower-143.messagelabs.com!1419367175!36473369!1
20             # X-Originating-IP: [10.245.230.38]
21             # X-StarScan-Received:
22             # X-StarScan-Version: 6.12.5; banners=-,-,-
23             # X-VirusChecked: Checked
24 218 100       870 return undef unless defined $mhead->{'x-msg-ref'};
25 16 50       117 return undef unless rindex($mhead->{'from'}, 'MAILER-DAEMON@messagelabs.com') > -1;
26 16 50       71 return undef unless index($mhead->{'subject'}, 'Mail Delivery Failure') == 0;
27              
28 16         48 state $indicators = __PACKAGE__->INDICATORS;
29 16         41 state $rebackbone = qr|^Content-Type:[ ]text/rfc822-headers|m;
30 16         32 state $startingof = { 'message' => ['Content-Type: message/delivery-status'] };
31 16         35 state $refailures = {
32             'userunknown' => qr/(?:542 .+ Rejected|No such user)/,
33             'securityerror' => qr/Please turn on SMTP Authentication in your mail client/,
34             };
35              
36 16         487 require Sisimai::RFC1894;
37 16         99 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
38 16         41 my $permessage = {}; # (Hash) Store values of each Per-Message field
39              
40 16         86 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
41 16         103 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
42 16         90 my $readcursor = 0; # (Integer) Points the current cursor position
43 16         42 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
44 16         35 my $v = undef;
45 16         59 my $p = '';
46              
47 16         205 for my $e ( split("\n", $emailsteak->[0]) ) {
48             # Read error messages and delivery status lines from the head of the email
49             # to the previous line of the beginning of the original message.
50 461 100       657 unless( $readcursor ) {
51             # Beginning of the bounce message or message/delivery-status part
52 316 100       627 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
53 316         341 next;
54             }
55 145 50       290 next unless $readcursor & $indicators->{'deliverystatus'};
56 145 100       223 next unless length $e;
57              
58 129 100       256 if( my $f = Sisimai::RFC1894->match($e) ) {
59             # $e matched with any field defined in RFC3464
60 117 50       262 next unless my $o = Sisimai::RFC1894->field($e);
61 117         186 $v = $dscontents->[-1];
62              
63 117 100       262 if( $o->[-1] eq 'addr' ) {
    100          
64             # Final-Recipient: rfc822; kijitora@example.jp
65             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
66 16 50       101 if( $o->[0] eq 'final-recipient' ) {
67             # Final-Recipient: rfc822; kijitora@example.jp
68 16 50       55 if( $v->{'recipient'} ) {
69             # There are multiple recipient addresses in the message body.
70 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
71 0         0 $v = $dscontents->[-1];
72             }
73 16         29 $v->{'recipient'} = $o->[2];
74 16         63 $recipients++;
75              
76             } else {
77             # X-Actual-Recipient: rfc822; kijitora@example.co.jp
78 0         0 $v->{'alias'} = $o->[2];
79             }
80             } elsif( $o->[-1] eq 'code' ) {
81             # Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
82 16         54 $v->{'spec'} = $o->[1];
83 16         58 $v->{'diagnosis'} = $o->[2];
84              
85             } else {
86             # Other DSN fields defined in RFC3464
87 85 50       173 next unless exists $fieldtable->{ $o->[0] };
88 85         215 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
89              
90 85 100       200 next unless $f == 1;
91 32         104 $permessage->{ $fieldtable->{ $o->[0] } } = $o->[2];
92             }
93             } else {
94             # Continued line of the value of Diagnostic-Code field
95 12 100       51 next unless index($p, 'Diagnostic-Code:') == 0;
96 6 50       31 next unless $e =~ /\A[ \t]+(.+)\z/;
97 0         0 $v->{'diagnosis'} .= ' '.$1;
98             } # End of message/delivery-status
99             } continue {
100             # Save the current line for the next loop
101 461         657 $p = $e;
102             }
103 16 50       87 return undef unless $recipients;
104              
105 16         37 for my $e ( @$dscontents ) {
106             # Set default values if each value is empty.
107 16   33     104 $e->{'lhost'} ||= $permessage->{'rhost'};
108 16   0     130 $e->{ $_ } ||= $permessage->{ $_ } || '' for keys %$permessage;
      33        
109 16         114 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
110              
111 16         123 SESSION: for my $r ( keys %$refailures ) {
112             # Verify each regular expression of session errors
113 26 100       232 next unless $e->{'diagnosis'} =~ $refailures->{ $r };
114 16         37 $e->{'reason'} = $r;
115 16         35 last;
116             }
117             }
118 16         141 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
119             }
120              
121             1;
122             __END__