File Coverage

lib/Sisimai/Lhost/MailMarshalSMTP.pm
Criterion Covered Total %
statement 50 52 96.1
branch 29 32 90.6
condition 6 7 85.7
subroutine 6 6 100.0
pod 2 2 100.0
total 93 99 93.9


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::MailMarshalSMTP;
2 15     15   6557 use parent 'Sisimai::Lhost';
  15         32  
  15         83  
3 15     15   978 use feature ':5.10';
  15         36  
  15         1020  
4 15     15   84 use strict;
  15         27  
  15         289  
5 15     15   64 use warnings;
  15         31  
  15         12346  
6              
7 2     2 1 1197 sub description { 'Trustwave Secure Email Gateway' }
8             sub make {
9             # Detect an error from MailMarshalSMTP
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.9
15 213     213 1 760 my $class = shift;
16 213   100     715 my $mhead = shift // return undef;
17 212   50     642 my $mbody = shift // return undef;
18 212 100       950 return undef unless index($mhead->{'subject'}, 'Undeliverable Mail: "') == 0;
19              
20 6         36 state $indicators = __PACKAGE__->INDICATORS;
21 6         20 state $rebackbone = qr/^[ \t]*[+]+[ \t]*/m;
22 6         21 state $startingof = {
23             'message' => ['Your message:'],
24             'error' => ['Could not be delivered because of'],
25             'rcpts' => ['The following recipients were affected:'],
26             };
27              
28 6         29 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
29 6         17 my $readcursor = 0; # (Integer) Points the current cursor position
30 6         12 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
31 6         12 my $endoferror = 0; # (Integer) Flag for the end of error message
32 6         13 my $v = undef;
33              
34 6 50       26 if( my $boundary00 = Sisimai::MIME->boundary($mhead->{'content-type'}, 1) ) {
35             # Convert to regular expression
36 6         121 $rebackbone = qr/^\Q$boundary00\E/m;
37             }
38 6         44 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
39              
40 6         65 for my $e ( split("\n", $emailsteak->[0]) ) {
41             # Read error messages and delivery status lines from the head of the email
42             # to the previous line of the beginning of the original message.
43 132 100       213 unless( $readcursor ) {
44             # Beginning of the bounce message or message/delivery-status part
45 12 100       46 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0;
46             }
47 132 100       200 next unless $readcursor & $indicators->{'deliverystatus'};
48              
49             # Your message:
50             # From: originalsender@example.com
51             # Subject: ...
52             #
53             # Could not be delivered because of
54             #
55             # 550 5.1.1 User unknown
56             #
57             # The following recipients were affected:
58             # dummyuser@blabla.xxxxxxxxxxxx.com
59 126         148 $v = $dscontents->[-1];
60              
61 126 100       222 if( $e =~ /\A[ \t]{4}([^ ]+[@][^ ]+)\z/ ) {
62             # The following recipients were affected:
63             # dummyuser@blabla.xxxxxxxxxxxx.com
64 6 50       24 if( $v->{'recipient'} ) {
65             # There are multiple recipient addresses in the message body.
66 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
67 0         0 $v = $dscontents->[-1];
68             }
69 6         16 $v->{'recipient'} = $1;
70 6         11 $recipients++;
71              
72             } else {
73             # Get error message lines
74 120 100 100     343 if( $e eq $startingof->{'error'}->[0] ) {
    100          
75             # Could not be delivered because of
76             #
77             # 550 5.1.1 User unknown
78 6         13 $v->{'diagnosis'} = $e;
79              
80             } elsif( $v->{'diagnosis'} && ! $endoferror ) {
81             # Append error messages
82 24 100       65 $endoferror = 1 if index($e, $startingof->{'rcpts'}->[0]) == 0;
83 24 100       45 next if $endoferror;
84              
85 18         38 $v->{'diagnosis'} .= ' '.$e;
86              
87             } else {
88             # Additional Information
89             # ======================
90             # Original Sender:
91             # Sender-MTA: <10.11.12.13>
92             # Remote-MTA: <10.0.0.1>
93             # Reporting-MTA:
94             # MessageName:
95             # Last-Attempt-Date: <16:21:07 seg, 22 Dezembro 2014>
96 90 100       300 if( $e =~ /\AOriginal Sender:[ \t]+[<](.+)[>]\z/ ) {
    100          
    100          
    100          
97             # Original Sender:
98             # Use this line instead of "From" header of the original
99             # message.
100 6         28 $emailsteak->[1] .= sprintf("From: %s\n", $1);
101              
102             } elsif( $e =~ /\ASender-MTA:[ \t]+[<](.+)[>]\z/ ) {
103             # Sender-MTA: <10.11.12.13>
104 6         18 $v->{'lhost'} = $1;
105              
106             } elsif( $e =~ /\AReporting-MTA:[ \t]+[<](.+)[>]\z/ ) {
107             # Reporting-MTA:
108 6         15 $v->{'rhost'} = $1;
109              
110             } elsif( $e =~ /\A\s+(From|Subject):\s*(.+)\z/ ) {
111             # From: originalsender@example.com
112             # Subject: ...
113 12         65 $emailsteak->[1] .= sprintf("%s: %s\n", $1, $2);
114             }
115             }
116             }
117             }
118 6 50       28 return undef unless $recipients;
119              
120 6         53 $_->{'diagnosis'} = Sisimai::String->sweep($_->{'diagnosis'}) for @$dscontents;
121 6         47 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
122             }
123              
124             1;
125             __END__