File Coverage

lib/Sisimai/Lhost/EZweb.pm
Criterion Covered Total %
statement 82 86 95.3
branch 43 56 76.7
condition 20 24 83.3
subroutine 6 6 100.0
pod 2 2 100.0
total 153 174 87.9


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::EZweb;
2 35     35   4814 use parent 'Sisimai::Lhost';
  35         77  
  35         258  
3 35     35   3303 use v5.26;
  35         133  
4 35     35   188 use strict;
  35         78  
  35         1029  
5 35     35   176 use warnings;
  35         68  
  35         51122  
6              
7 1     1 1 8 sub description { 'au EZweb: https://www.au.com/mobile/' }
8             sub inquire {
9             # Detect an error from EZweb
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 decode or the arguments are missing
14             # @since v4.0.0
15 908     908 1 4688 my $class = shift;
16 908   100     3138 my $mhead = shift // return undef;
17 907   100     2553 my $mbody = shift // return undef;
18              
19             # Pre-process email headers of NON-STANDARD bounce message au by EZweb, as known as ezweb.ne.jp.
20             # Subject: Mail System Error - Returned Mail
21             # From:
22             # Received: from ezweb.ne.jp (wmflb12na02.ezweb.ne.jp [222.15.69.197])
23             # Received: from nmomta.auone-net.jp ([aaa.bbb.ccc.ddd]) by ...
24 906 100       2304 my $match = 0; $match++ if rindex($mhead->{'from'}, 'Postmaster@ezweb.ne.jp') > -1;
  906         3860  
25 906 50       3014 $match++ if rindex($mhead->{'from'}, 'Postmaster@au.com') > -1;
26 906 100       2692 $match++ if $mhead->{'subject'} eq 'Mail System Error - Returned Mail';
27 906 100       3077 $match++ if grep { rindex($_, 'ezweb.ne.jp (EZweb Mail) with') > -1 } $mhead->{'received'}->@*;
  1723         5120  
28 906 50       2147 $match++ if grep { rindex($_, '.au.com (') > -1 } $mhead->{'received'}->@*;
  1723         4441  
29 906 100       3316 if( defined $mhead->{'message-id'} ) {
30 845 100       3528 $match++ if substr($mhead->{'message-id'}, -13, 13) eq '.ezweb.ne.jp>';
31 845 50       2661 $match++ if substr($mhead->{'message-id'}, -8, 8) eq '.au.com>';
32             }
33 906 100       3796 return undef if $match < 2;
34              
35 41         244 require Sisimai::SMTP::Command;
36 41         109 state $indicators = __PACKAGE__->INDICATORS;
37 41         82 state $boundaries = ["--------------------------------------------------", "Content-Type: message/rfc822"];
38 41         175 state $startingof = {"message" => ['The user(s) ', 'Your message ', 'Each of the following', '<']};
39 41         106 state $messagesof = {
40             #'notaccept' => ['The following recipients did not receive this message:'],
41             'expired' => [
42             # Your message was not delivered within 0 days and 1 hours.
43             # Remote host is not responding.
44             'Your message was not delivered within ',
45             ],
46             'mailboxfull' => ['The user(s) account is temporarily over quota'],
47             'onhold' => ['Each of the following recipients was rejected by a remote mail server'],
48             'suspend' => [
49             # http://www.naruhodo-au.kddi.com/qa3429203.html
50             # The recipient may be unpaid user...?
51             'The user(s) account is disabled.',
52             'The user(s) account is temporarily limited.',
53             ],
54             };
55              
56 41         312 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
57 41         250 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  41         135  
58 41         275 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
59 41         87 my $readcursor = 0; # Points the current cursor position
60 41         70 my $recipients = 0; # The number of 'Final-Recipient' header
61 41         97 my $substrings = []; # All the values of "messagesof"
62 41         223 map { push @$substrings, $messagesof->{ $_ }->@* } keys %$messagesof;
  164         433  
63              
64 41         401 for my $e ( split("\n", $emailparts->[0]) ) {
65             # Read error messages and delivery status lines from the head of the email to the previous
66             # line of the beginning of the original message.
67 533 100       934 unless( $readcursor ) {
68             # Beginning of the bounce message or message/delivery-status part
69 301 100       1482 $readcursor |= $indicators->{'deliverystatus'} if grep { index($e, $_) > -1 } $startingof->{'message'}->@*;
  1204         2109  
70             }
71 533 100 100     1745 next if ($readcursor & $indicators->{'deliverystatus'}) == 0 || $e eq "";
72              
73             # The user(s) account is disabled.
74             #
75             # <***@ezweb.ne.jp>: 550 user unknown (in reply to RCPT TO command)
76             #
77             # -- OR --
78             # Each of the following recipients was rejected by a remote
79             # mail server.
80             #
81             # Recipient: <******@ezweb.ne.jp>
82             # >>> RCPT TO:<******@ezweb.ne.jp>
83             # <<< 550 <******@ezweb.ne.jp>: User unknown
84 197         309 $v = $dscontents->[-1];
85              
86 197 100 100     1003 if( Sisimai::String->aligned(\$e, ['<', '@', '>']) && (index($e, 'Recipient: <') > 1 || index($e, '<') == 0) ) {
    100 100        
87             # Recipient: <******@ezweb.ne.jp> OR <***@ezweb.ne.jp>: 550 user unknown ...
88 41         92 my $p1 = index($e, '<');
89 41         87 my $p2 = index($e, '>');
90              
91 41 50       167 if( $v->{'recipient'} ) {
92             # There are multiple recipient addresses in the message body.
93 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
94 0         0 $v = $dscontents->[-1];
95             }
96 41         472 $v->{'recipient'} = Sisimai::Address->s3s4(substr($e, $p1, $p2 - $p1));
97 41         175 $v->{"diagnosis"} .= " ".$e;
98 41         133 $recipients++;
99              
100             } elsif( my $f = Sisimai::RFC1894->match($e) ) {
101             # $e matched with any field defined in RFC3464
102 50 50       139 next unless my $o = Sisimai::RFC1894->field($e);
103 50 50       149 next unless exists $fieldtable->{ $o->[0] };
104 50         202 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
105              
106             } else {
107             # The line does not begin with a DSN field defined in RFC3464
108 106 50       372 next if Sisimai::String->is_8bit(\$e);
109 106 100       418 if( index($e, " >>> ") > -1 ) {
    100          
110             # >>> RCPT TO:<******@ezweb.ne.jp>
111 15         167 $v->{"command"} = Sisimai::SMTP::Command->find($e);
112 15         97 $v->{"diagnosis"} .= " ".$e;
113              
114             } elsif( index($e, " <<< ") > -1 ) {
115             # <<< 550 ...
116 15         102 $v->{"diagnosis"} .= " ".$e;
117              
118             } else {
119             # Check error message
120 76         152 my $isincluded = 0;
121 76 100       160 if( grep { index($e, $_) > -1 } @$substrings ) {
  380         729  
122             # Try to find that the line contains any error message text
123 26         110 $v->{"diagnosis"} .= ' '.$e;
124 26         53 $isincluded = 1;
125             }
126 76 100       387 $v->{"diagnosis"} .= " ".$e if $isincluded == 0;
127             }
128             } # End of error message part
129             }
130 41 50       258 return undef unless $recipients;
131              
132 41         110 for my $e ( @$dscontents ) {
133             # Check each value of DeliveryMatter{}, try to detect the bounce reason.
134 41         193 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'});
135 41   100     434 $e->{"command"} ||= Sisimai::SMTP::Command->find($e->{"diagnosis"}) || "";
      100        
136              
137 41 100 66     277 if( defined $mhead->{'x-spasign'} && $mhead->{'x-spasign'} eq 'NG' ) {
138             # Content-Type: text/plain; ..., X-SPASIGN: NG (spamghetti, au by EZweb)
139             # Filtered recipient returns message that include 'X-SPASIGN' header
140 6         21 $e->{'reason'} = 'filtered';
141 6         19 $e->{'toxic'} = 1;
142              
143             } else {
144             # There is no X-SPASIGN header or the value of the header is not "NG"
145 35         140 FINDREASON: for my $r ( keys %$messagesof ) {
146             # Try to match with each session error message
147 71         154 for my $f ( $messagesof->{ $r }->@* ) {
148             # Check each error message pattern
149 87 100       256 next unless index($e->{'diagnosis'}, $f) > -1;
150 35         77 $e->{'reason'} = $r;
151 35         139 last FINDREASON;
152             }
153             }
154             }
155 41 50       163 next if $e->{'reason'};
156 0 0 0     0 next if index($e->{'recipient'}, '@ezweb.ne.jp') > 1 || index($e->{'recipient'}, '@au.com') > 1;
157 0 0       0 $e->{"reason"} = "userunknown" if index($e->{"diagnosis"}, "<") == 0;
158             }
159 41         338 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
160             }
161              
162             1;
163             __END__