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   5884 use parent 'Sisimai::Lhost';
  18         36  
  18         91  
3 18     18   1486 use feature ':5.10';
  18         39  
  18         1223  
4 18     18   106 use strict;
  18         39  
  18         430  
5 18     18   93 use warnings;
  18         35  
  18         15257  
6              
7 2     2 1 1136 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 734 my $class = shift;
16 225   100     626 my $mhead = shift // return undef;
17 224   50     476 my $mbody = shift // return undef;
18              
19             # X-Mailer: m-FILTER
20 224 100       776 return undef unless defined $mhead->{'x-mailer'};
21 27 100       129 return undef unless $mhead->{'x-mailer'} eq 'm-FILTER';
22 21 50       75 return undef unless $mhead->{'subject'} eq 'failure notice';
23              
24 21         61 state $indicators = __PACKAGE__->INDICATORS;
25 21         46 state $rebackbone = qr/^-------original[ ](?:message|mail[ ]info)/m;
26 21         49 state $startingof = {
27             'command' => ['-------SMTP command'],
28             'error' => ['-------server message'],
29             };
30 21         45 state $markingsof = { 'message' => qr/\A[^ ]+[@][^ ]+[.][a-zA-Z]+\z/ };
31              
32 21         75 my $dscontents = [__PACKAGE__->DELIVERYSTATUS];
33 21         97 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone);
34 21         58 my $readcursor = 0; # (Integer) Points the current cursor position
35 21         54 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header
36 21         76 my $markingset = { 'diagnosis' => 0, 'command' => 0 };
37 21         41 my $v = undef;
38              
39 21         130 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       379 unless( $readcursor ) {
43             # Beginning of the bounce message or message/delivery-status part
44 126 100       491 $readcursor |= $indicators->{'deliverystatus'} if $e =~ $markingsof->{'message'};
45             }
46 273 100       536 next unless $readcursor & $indicators->{'deliverystatus'};
47 168 100       302 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         125 $v = $dscontents->[-1];
65              
66 105 100       380 if( $e =~ /\A([^ ]+[@][^ ]+)\z/ ) {
    100          
67             # 以下のメールアドレスへの送信に失敗しました。
68             # kijitora@example.jp
69 21 50       80 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         77 $v->{'recipient'} = $1;
75 21         40 $recipients++;
76              
77             } elsif( $e =~ /\A[A-Z]{4}/ ) {
78             # -------SMTP command
79             # DATA
80 21 50       58 next if $v->{'command'};
81 21 50       93 $v->{'command'} = $e if $markingset->{'command'};
82              
83             } else {
84             # Get error message and SMTP command
85 63 100       236 if( $e eq $startingof->{'error'}->[0] ) {
    100          
86             # -------server message
87 21         53 $markingset->{'diagnosis'} = 1;
88              
89             } elsif( $e eq $startingof->{'command'}->[0] ) {
90             # -------SMTP command
91 21         54 $markingset->{'command'} = 1;
92              
93             } else {
94             # 550 5.1.1 unknown user
95 21 50       64 next if index($e, '-') == 0;
96 21 50       67 next if $v->{'diagnosis'};
97 21         45 $v->{'diagnosis'} = $e;
98             }
99             } # End of error message part
100             }
101 21 50       90 return undef unless $recipients;
102              
103 21         51 for my $e ( @$dscontents ) {
104 21         153 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
105              
106             # Get localhost and remote host name from Received header.
107 21 50       64 next unless scalar @{ $mhead->{'received'} };
  21         92  
108 21         61 my $rheads = $mhead->{'received'};
109 21         103 my $rhosts = Sisimai::RFC5322->received($rheads->[-1]);
110              
111 21   33     195 $e->{'lhost'} ||= shift @{ Sisimai::RFC5322->received($rheads->[0]) };
  21         61  
112 21         108 for my $ee ( @$rhosts ) {
113             # Avoid "... by m-FILTER"
114 42 100       127 next unless rindex($ee, '.') > -1;
115 21         51 $e->{'rhost'} = $ee;
116             }
117             }
118 21         126 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] };
119             }
120              
121             1;
122             __END__