File Coverage

lib/Sisimai/Lhost/mFILTER.pm
Criterion Covered Total %
statement 60 62 96.7
branch 30 38 78.9
condition 4 7 57.1
subroutine 6 6 100.0
pod 2 2 100.0
total 102 115 88.7


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::mFILTER;
2 18     18   5470 use parent 'Sisimai::Lhost';
  18         39  
  18         92  
3 18     18   1003 use feature ':5.10';
  18         27  
  18         1468  
4 18     18   82 use strict;
  18         26  
  18         331  
5 18     18   72 use warnings;
  18         28  
  18         12759  
6              
7 2     2 1 1146 sub description { 'Digital Arts m-FILTER' }
8             sub make {
9             # Detect an error from DigitalArts m-FILTER
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.1
15 225     225 1 626 my $class = shift;
16 225   100     620 my $mhead = shift // return undef;
17 224   50     414 my $mbody = shift // return undef;
18              
19             # X-Mailer: m-FILTER
20 224 100       587 return undef unless defined $mhead->{'x-mailer'};
21 27 100       92 return undef unless $mhead->{'x-mailer'} eq 'm-FILTER';
22 21 50       77 return undef unless $mhead->{'subject'} eq 'failure notice';
23              
24 21         59 state $indicators = __PACKAGE__->INDICATORS;
25 21         35 state $rebackbone = qr/^-------original[ ](?:message|mail[ ]info)/m;
26 21         38 state $startingof = {
27             'command' => ['-------SMTP command'],
28             'error' => ['-------server message'],
29             };
30 21         40 state $markingsof = { 'message' => qr/\A[^ ]+[@][^ ]+[.][a-zA-Z]+\z/ };
31              
32 21         74 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
33 21         67 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
34 21         31 my $readcursor = 0; # (Integer) Points the current cursor position
35 21         33 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
36 21         68 my $markingset = { 'diagnosis' => 0, 'command' => 0 };
37 21         46 my $v = undef;
38              
39 21         114 for my $e ( split("\n", $emailsteak->[0]) ) {
40             # Read error messages and delivery status lines from the head of the email
41             # to the previous line of the beginning of the original message.
42 273 100       304 unless( $readcursor ) {
43             # Beginning of the bounce message or message/delivery-status part
44 126 100       395 $readcursor |= $indicators->{'deliverystatus'} if $e =~ $markingsof->{'message'};
45             }
46 273 100       374 next unless $readcursor & $indicators->{'deliverystatus'};
47 168 100       199 next unless length $e;
48              
49             # このメールは「m-FILTER」が自動的に生成して送信しています。
50             # メールサーバーとの通信中、下記の理由により
51             # このメールは送信できませんでした。
52             #
53             # 以下のメールアドレスへの送信に失敗しました。
54             # kijitora@example.jp
55             #
56             #
57             # -------server message
58             # 550 5.1.1 unknown user
59             #
60             # -------SMTP command
61             # DATA
62             #
63             # -------original message
64 105         99 $v = $dscontents->[-1];
65              
66 105 100       303 if( $e =~ /\A([^ ]+[@][^ ]+)\z/ ) {
    100          
67             # 以下のメールアドレスへの送信に失敗しました。
68             # kijitora@example.jp
69 21 50       61 if( $v->{'recipient'} ) {
70             # There are multiple recipient addresses in the message body.
71 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
72 0         0 $v = $dscontents->[-1];
73             }
74 21         61 $v->{'recipient'} = $1;
75 21         36 $recipients++;
76              
77             } elsif( $e =~ /\A[A-Z]{4}/ ) {
78             # -------SMTP command
79             # DATA
80 21 50       57 next if $v->{'command'};
81 21 50       71 $v->{'command'} = $e if $markingset->{'command'};
82              
83             } else {
84             # Get error message and SMTP command
85 63 100       185 if( $e eq $startingof->{'error'}->[0] ) {
    100          
86             # -------server message
87 21         38 $markingset->{'diagnosis'} = 1;
88              
89             } elsif( $e eq $startingof->{'command'}->[0] ) {
90             # -------SMTP command
91 21         31 $markingset->{'command'} = 1;
92              
93             } else {
94             # 550 5.1.1 unknown user
95 21 50       67 next if index($e, '-') == 0;
96 21 50       58 next if $v->{'diagnosis'};
97 21         43 $v->{'diagnosis'} = $e;
98             }
99             } # End of error message part
100             }
101 21 50       61 return undef unless $recipients;
102              
103 21         52 for my $e ( @$dscontents ) {
104 21         147 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
105              
106             # Get localhost and remote host name from Received header.
107 21 50       41 next unless scalar @{ $mhead->{'received'} };
  21         1498  
108 21         41 my $rheads = $mhead->{'received'};
109 21         75 my $rhosts = Sisimai::RFC5322->received($rheads->[-1]);
110              
111 21   33     74 $e->{'lhost'} ||= shift @{ Sisimai::RFC5322->received($rheads->[0]) };
  21         62  
112 21         49 for my $ee ( @$rhosts ) {
113             # Avoid "... by m-FILTER"
114 42 100       99 next unless rindex($ee, '.') > -1;
115 21         38 $e->{'rhost'} = $ee;
116             }
117             }
118 21         112 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
119             }
120              
121             1;
122             __END__