File Coverage

lib/Sisimai/Reason.pm
Criterion Covered Total %
statement 93 95 97.8
branch 55 62 88.7
condition 30 43 69.7
subroutine 9 9 100.0
pod 1 6 16.6
total 188 215 87.4


line stmt bran cond sub pod time code
1             package Sisimai::Reason;
2 76     76   1202 use feature ':5.10';
  76         119  
  76         5001  
3 76     76   380 use strict;
  76         118  
  76         1316  
4 76     76   311 use warnings;
  76         116  
  76         88527  
5              
6             my $ModulePath = __PACKAGE__->path;
7             my $GetRetried = __PACKAGE__->retry;
8             my $ClassOrder = [
9             [qw|MailboxFull MesgTooBig ExceedLimit Suspend HasMoved NoRelaying UserUnknown
10             Filtered Rejected HostUnknown SpamDetected TooManyConn Blocked
11             |],
12             [qw|MailboxFull SpamDetected PolicyViolation VirusDetected NoRelaying
13             SecurityError SystemError NetworkError Suspend Expired ContentError
14             SystemFull NotAccept MailerError
15             |],
16             [qw|MailboxFull MesgTooBig ExceedLimit Suspend UserUnknown Filtered Rejected
17             HostUnknown SpamDetected TooManyConn Blocked SpamDetected SecurityError
18             SystemError NetworkError Suspend Expired ContentError HasMoved SystemFull
19             NotAccept MailerError NoRelaying SyntaxError OnHold
20             |],
21             ];
22              
23             sub retry {
24             # Reason list better to retry detecting an error reason
25             # @return [Array] Reason list
26             return {
27 151     151 0 866 'undefined' => 1, 'onhold' => 1, 'systemerror' => 1, 'securityerror' => 1,
28             'networkerror' => 1, 'hostunknown' => 1, 'userunknown'=> 1
29             };
30             }
31              
32             sub index {
33             # All the error reason list Sisimai support
34             # @return [Array] Reason list
35 81     81 0 964 return [qw|
36             Blocked ContentError ExceedLimit Expired Filtered HasMoved HostUnknown
37             MailboxFull MailerError MesgTooBig NetworkError NotAccept OnHold
38             Rejected NoRelaying SpamDetected VirusDetected PolicyViolation SecurityError
39             Suspend SystemError SystemFull TooManyConn UserUnknown SyntaxError
40             |];
41             }
42              
43             sub path {
44             # Returns Sisimai::Reason::* module path table
45             # @return [Hash] Module path table
46             # @since v4.25.6
47 77     77 0 184 my $class = shift;
48 77         211 my $index = __PACKAGE__->index;
49 77         144 my $table = {};
50 77         2299 $table->{ __PACKAGE__.'::'.$_ } = 'Sisimai/Reason/'.$_.'.pm' for @$index;
51 77         236 return $table;
52             }
53              
54             sub get {
55             # Detect the bounce reason
56             # @param [Sisimai::Data] argvs Parsed email object
57             # @return [String, Undef] Bounce reason or Undef if the argument
58             # is missing or invalid object
59             # @see anotherone
60 2100     2100 0 4738 my $class = shift;
61 2100   100     4464 my $argvs = shift // return undef;
62 2099 50       4396 return undef unless ref $argvs eq 'Sisimai::Data';
63              
64 2099 100       4865 unless( exists $GetRetried->{ $argvs->reason } ) {
65             # Return reason text already decided except reason match with the
66             # regular expression of ->retry() method.
67 1710 50       9989 return $argvs->reason if $argvs->reason;
68             }
69 2099 100       10336 return 'delivered' if substr($argvs->deliverystatus, 0, 2) eq '2.';
70              
71 2097         9664 my $reasontext = '';
72 2097 100 66     4894 if( $argvs->diagnostictype eq 'SMTP' || $argvs->diagnostictype eq '' ) {
73             # Diagnostic-Code: SMTP; ... or empty value
74 2016         8923 for my $e ( @{ $ClassOrder->[0] } ) {
  2016         4447  
75             # Check the value of Diagnostic-Code: and the value of Status:, it is a
76             # deliverystats, with true() method in each Sisimai::Reason::* class.
77 18026         30796 my $p = 'Sisimai::Reason::'.$e;
78 18026         254272 require $ModulePath->{ $p };
79              
80 18026 100       73287 next unless $p->true($argvs);
81 1517         5761 $reasontext = $p->text;
82 1517         2440 last;
83             }
84             }
85              
86 2097 100 66     6869 if( not $reasontext || $reasontext eq 'undefined' ) {
87             # Bounce reason is not detected yet.
88 580         1493 $reasontext = __PACKAGE__->anotherone($argvs);
89 580 50       1649 $reasontext = '' if $reasontext eq 'undefined';
90 580 100 50     1272 $reasontext ||= 'expired' if $argvs->action eq 'delayed';
91 580 100       3870 return $reasontext if $reasontext;
92              
93             # Try to match with message patterns in Sisimai::Reason::Vacation
94 31         2521 require Sisimai::Reason::Vacation;
95 31 50       97 $reasontext = 'vacation' if Sisimai::Reason::Vacation->match(lc $argvs->diagnosticcode);
96 31 100 50     83 $reasontext ||= 'onhold' if $argvs->diagnosticcode;
97 31   100     225 $reasontext ||= 'undefined';
98             }
99 1548         4982 return $reasontext;
100             }
101              
102             sub anotherone {
103             # Detect the other bounce reason, fall back method for get()
104             # @param [Sisimai::Data] argvs Parsed email object
105             # @return [String, Undef] Bounce reason or Undef if the argument
106             # is missing or invalid object
107             # @see get
108 581     581 0 782 my $class = shift;
109 581   100     1235 my $argvs = shift // return undef;
110              
111 580 50       1320 return undef unless ref $argvs eq 'Sisimai::Data';
112 580 100       1179 return $argvs->reason if $argvs->reason;
113              
114 493         2890 require Sisimai::SMTP::Status;
115 493   50     950 my $statuscode = $argvs->deliverystatus // '';
116 493   100     2570 my $reasontext = Sisimai::SMTP::Status->name($statuscode) || '';
117              
118 493         632 TRY_TO_MATCH: while(1) {
119 493   50     1170 my $diagnostic = lc $argvs->diagnosticcode // '';
120 493 100       2743 my $trytomatch = $reasontext eq '' ? 1 : 0;
121 493 100 50     1370 $trytomatch ||= 1 if exists $GetRetried->{ $reasontext };
122 493 100 100     1087 $trytomatch ||= 1 if $argvs->diagnostictype ne 'SMTP';
123 493 100       2451 last unless $trytomatch;
124              
125             # Could not decide the reason by the value of Status:
126 379         513 for my $e ( @{ $ClassOrder->[1] } ) {
  379         844  
127             # Trying to match with other patterns in Sisimai::Reason::* classes
128 3330         4401 my $p = 'Sisimai::Reason::'.$e;
129 3330         61373 require $ModulePath->{ $p };
130              
131 3330 100       13359 next unless $p->match($diagnostic);
132 268         596 $reasontext = lc $e;
133 268         411 last;
134             }
135 379 100       1422 last(TRY_TO_MATCH) if $reasontext;
136              
137             # Check the value of Status:
138 75 50 33     779 if( (my $v = substr($statuscode, 0, 3)) =~ /\A[45][.]6\z/ ) {
    50 66        
    100          
139             # X.6.0 Other or undefined media error
140 0         0 $reasontext = 'contenterror';
141              
142             } elsif( $v eq '5.7' || $v eq '4.7' ) {
143             # X.7.0 Other or undefined security status
144 0         0 $reasontext = 'securityerror';
145              
146             } elsif( $argvs->diagnostictype eq 'X-UNIX' || $argvs->diagnostictype eq 'X-POSTFIX' ) {
147             # Diagnostic-Code: X-UNIX; ...
148 14         97 $reasontext = 'mailererror';
149              
150             } else {
151             # 50X Syntax Error?
152 61         3553 require Sisimai::Reason::SyntaxError;
153 61 100       364 $reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError->true($argvs);
154             }
155 75 100       335 last(TRY_TO_MATCH) if $reasontext;
156              
157             # Check the value of Action: field, first
158 51 100       119 if( $argvs->action =~ /\A(?:delayed|expired)/ ) {
159             # Action: delayed, expired
160 15         118 $reasontext = 'expired';
161             } else {
162             # Check the value of SMTP command
163 36   50     227 my $commandtxt = $argvs->smtpcommand // '';
164 36 100 66     321 if( $commandtxt eq 'EHLO' || $commandtxt eq 'HELO' ) {
165             # Rejected at connection or after EHLO|HELO
166 5         10 $reasontext = 'blocked';
167             }
168             }
169 51         111 last(TRY_TO_MATCH);
170             }
171 493         958 return $reasontext;
172             }
173              
174             sub match {
175             # Detect the bounce reason from given text
176             # @param [String] argv1 Error message
177             # @return [String] Bounce reason
178 186     186 1 112477 my $class = shift;
179 186   50     353 my $argv1 = shift // return undef;
180              
181 186         180 my $reasontext = '';
182 186         246 my $diagnostic = lc $argv1;
183              
184             # Diagnostic-Code: SMTP; ... or empty value
185 186         182 for my $e ( @{ $ClassOrder->[2] } ) {
  186         266  
186             # Check the value of Diagnostic-Code: and the value of Status:, it is a
187             # deliverystats, with true() method in each Sisimai::Reason::* class.
188 2144         2377 my $p = 'Sisimai::Reason::'.$e;
189 2144         5592 require $ModulePath->{ $p };
190              
191 2144 100       4015 next unless $p->match($diagnostic);
192 142         298 $reasontext = $p->text;
193 142         174 last;
194             }
195 186 100       405 return $reasontext if $reasontext;
196              
197             # Check the value of $typestring
198 44 100       243 my $typestring = uc($argv1) =~ /\A(SMTP|X-.+);/ ? uc($1) : '';
199 44 100       73 if( $typestring eq 'X-UNIX' ) {
200             # X-Unix; ...
201 2         3 $reasontext = 'mailererror';
202             } else {
203             # Detect the bounce reason from "Status:" code
204 42         95 require Sisimai::SMTP::Status;
205 42   100     114 my $statuscode = Sisimai::SMTP::Status->find($argv1) || '';
206 42   100     94 $reasontext = Sisimai::SMTP::Status->name($statuscode) || 'undefined';
207             }
208 44         106 return $reasontext;
209             }
210              
211             1;
212             __END__