File Coverage

lib/Sisimai/RFC1894.pm
Criterion Covered Total %
statement 68 70 97.1
branch 27 32 84.3
condition 18 25 72.0
subroutine 9 9 100.0
pod 3 3 100.0
total 125 139 89.9


line stmt bran cond sub pod time code
1             package Sisimai::RFC1894;
2 88     88   128007 use v5.26;
  88         303  
3 88     88   471 use strict;
  88         141  
  88         2290  
4 88     88   646 use warnings;
  88         382  
  88         4678  
5 88     88   43461 use Sisimai::String;
  88         409  
  88         6686  
6              
7 88         11169 use constant FIELDINDEX => [qw|
8             Action Arrival-Date Diagnostic-Code Final-Recipient Last-Attempt-Date Original-Recipient
9             Received-From-MTA Remote-MTA Reporting-MTA Status X-Actual-Recipient X-Original-Message-ID
10 88     88   870 |];
  88         180  
11 88         103341 use constant FIELDTABLE => {
12             # Return pairs that a field name and key name defined in Sisimai::Lhost class
13             'action' => 'action',
14             'arrival-date' => 'date',
15             'diagnostic-code' => 'diagnosis',
16             'final-recipient' => 'recipient',
17             'last-attempt-date' => 'date',
18             'original-recipient'=> 'alias',
19             'received-from-mta' => 'lhost',
20             'remote-mta' => 'rhost',
21             'reporting-mta' => 'lhost',
22             'status' => 'status',
23             'x-actual-recipient'=> 'alias',
24 88     88   542 };
  88         166  
25              
26             sub match {
27             # Check the argument matches with a field defined in RFC3464
28             # @param [String] argv0 A line including field and value defined in RFC3464
29             # @return [Integer] 0: did not matched, 1,2: matched
30             # @since v4.25.0
31 27691     27691 1 67235 my $class = shift;
32 27691   50     60151 my $argv0 = shift || return 0;
33 27691   50     63001 my $label = __PACKAGE__->label($argv0) || return 0;
34 27691         38443 my $match = 0;
35              
36 27691         37009 state $fieldnames = [
37             # https://tools.ietf.org/html/rfc3464#section-2.2
38             # Some fields of a DSN apply to all of the delivery attempts described by that DSN. At
39             # most, these fields may appear once in any DSN. These fields are used to correlate the
40             # DSN with the original message transaction and to provide additional information which
41             # may be useful to gateways.
42             #
43             # The following fields (not defined in RFC 3464) are used in Sisimai
44             # - X-Original-Message-ID: <....> (GSuite)
45             #
46             # The following fields are not used in Sisimai:
47             # - Original-Envelope-Id
48             # - DSN-Gateway
49             {
50             'arrival-date' => ':',
51             'received-from-mta' => ';',
52             'reporting-mta' => ';',
53             'x-original-message-id' => '@',
54             },
55              
56             # https://tools.ietf.org/html/rfc3464#section-2.3
57             # A DSN contains information about attempts to deliver a message to one or more recipi-
58             # ents. The delivery information for any particular recipient is contained in a group of
59             # contiguous per-recipient fields. Each group of per-recipient fields is preceded by a
60             # blank line.
61             #
62             # The following fields (not defined in RFC 3464) are used in Sisimai
63             # - X-Actual-Recipient: RFC822; ....
64             #
65             # The following fields are not used in Sisimai:
66             # - Will-Retry-Until
67             # - Final-Log-ID
68             {
69             'action' => 'e',
70             'diagnostic-code' => ';',
71             'final-recipient' => ';',
72             'last-attempt-date' => ':',
73             'original-recipient' => ';',
74             'remote-mta' => ';',
75             'status' => '.',
76             'x-actual-recipient' => ';',
77             },
78             ];
79              
80 27691         92313 FIELDS0: for my $e ( keys $fieldnames->[0]->%* ) {
81             # Per-Message fields
82 104890 100       173282 next unless $label eq $e;
83 4363 50       14954 next unless index($argv0, $fieldnames->[0]->{ $label }) > 1;
84 4363         6010 $match = 1; last;
  4363         5889  
85             }
86 27691 100       64290 return $match if $match > 0;
87              
88 23328         64122 FIELDS1: for my $e ( keys $fieldnames->[1]->%* ) {
89             # Per-Recipient fields
90 158044 100       260712 next unless $label eq $e;
91 10360 100       29984 next unless index($argv0, $fieldnames->[1]->{ $label }) > 1;
92 10298         13213 $match = 2; last;
  10298         13234  
93             }
94 23328         77653 return $match;
95             }
96              
97             sub label {
98             # Returns a field name as a label from the given string
99             # @param [String] argv0 A line including field and value defined in RFC3464
100             # @return [String] Field name as a label
101             # @since v4.25.15
102 44037     44037 1 83249 my $class = shift;
103 44037   50     81138 my $argv0 = shift || return "";
104 44037   50     204367 return lc((split(':', $argv0, 2))[0]) || "";
105             }
106              
107             sub field {
108             # Check the argument is including field defined in RFC3464 and return values
109             # @param [String] argv0 A line including field and value defined in RFC3464
110             # @return [Array] ['field-name', 'value-type', 'Value', 'field-group']
111             # @since v4.25.0
112 16332     16332 1 327660 my $class = shift;
113 16332   100     30899 my $argv0 = shift || return undef;
114              
115 16331         23513 state $subtypeset = {"addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS"};
116 16331         19278 state $actionlist = ["failed", "delayed", "delivered", "relayed", "expanded"];
117 16331         20877 state $correction = {'deliverable' => 'delivered', 'expired' => 'failed', 'failure' => 'failed'};
118 16331         20709 state $fieldgroup = {
119             'original-recipient' => 'addr',
120             'final-recipient' => 'addr',
121             'x-actual-recipient' => 'addr',
122             'diagnostic-code' => 'code',
123             'arrival-date' => 'date',
124             'last-attempt-date' => 'date',
125             'received-from-mta' => 'host',
126             'remote-mta' => 'host',
127             'reporting-mta' => 'host',
128             'action' => 'list',
129             'status' => 'stat',
130             'x-original-message-id' => 'text',
131             };
132 16331         22467 state $captureson = {
133             "addr" => ["Final-Recipient", "Original-Recipient", "X-Actual-Recipient"],
134             "code" => ["Diagnostic-Code"],
135             "date" => ["Arrival-Date", "Last-Attempt-Date"],
136             "host" => ["Received-From-MTA", "Remote-MTA", "Reporting-MTA"],
137             "list" => ["Action"],
138             "stat" => ["Status"],
139             #"text" => ["X-Original-Message-ID", "Final-Log-ID", "Original-Envelope-ID"],
140             };
141              
142 16331         50505 my $parts = [split(":", $argv0, 2)]; # ["Final-Recipient", " rfc822; "]
143 16331   50     41176 my $label = __PACKAGE__->label($argv0) || return undef;
144 16331   100     44648 my $group = $fieldgroup->{ $label } || return undef;
145 16326 100       37312 return undef unless exists $captureson->{ $group };
146              
147             # Try to match with each pattern of Per-Message field, Per-Recipient field
148             # - 0: Field-Name
149             # - 1: Sub Type: RFC822, DNS, X-Unix, and so on)
150             # - 2: Value
151             # - 3: Field Group(addr, code, date, host, stat, text)
152             # - 4: Comment
153 16161         40142 my $table = [$label, "", "", $group, ""]; $parts->[1] = Sisimai::String->sweep($parts->[1]);
  16161         64290  
154              
155 16161 100 100     91425 if( $group eq 'addr' || $group eq 'code' || $group eq 'host' ) {
    100 100        
156             # - Final-Recipient: RFC822; kijitora@nyaan.jp
157             # - Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
158             # - Remote-MTA: DNS; mx.example.jp
159 8160 50       17463 if( index($parts->[1], ";" ) > 0 ) {
160             # There is a valid sub type (including ";")
161 8160         25438 my $v = [split(";", $parts->[1], 2)];
162 8160 50       28439 $table->[1] = uc Sisimai::String->sweep($v->[0]) if scalar @$v > 0;
163 8160 50       27388 $table->[2] = Sisimai::String->sweep($v->[1]) if scalar @$v > 1;
164              
165             } else {
166             # There is no sub type like "Diagnostic-Code: 550 5.1.1 ..."
167 0         0 $table->[2] = Sisimai::String->sweep($parts->[1]);
168 0   0     0 $table->[1] = $subtypeset->{ $group } || "";
169             }
170 8160 100       21562 $table->[2] = lc $table->[2] if $group eq "host";
171 8160 50       39487 $table->[2] = '' if $table->[2] =~ /\A\s+\z/;
172              
173             } elsif( $group eq "list" ) {
174             # Action: failed
175             # Check that the value is an available value defined in "actionlist" or not.
176             # When the value is invalid, convert to an available value defined in "correction"
177 3604         8937 my $v = lc $parts->[1];
178 3604 100       9667 $table->[2] = $v if grep { $v eq $_ } @$actionlist;
  18020         37698  
179 3604   100     17851 $table->[2] ||= $correction->{ $v };
180              
181             } else {
182             # Other groups such as Status:, Arrival-Date:, or X-Original-Message-ID:.
183             # There is no ";" character in the field.
184             # - Status: 5.2.2
185             # - Arrival-Date: Mon, 21 May 2018 16:09:59 +0900
186 4397 100       11796 $table->[2] = $group eq "date" ? $parts->[1] : lc $parts->[1];
187             }
188              
189 16161 100       64083 if( Sisimai::String->aligned(\$table->[2], [" (", ")"]) ) {
190             # Extract text enclosed in parentheses as comments
191             # Reporting-MTA: dns; mr21p30im-asmtp004.me.example.com (tcp-daemon)
192 1739         3938 my $p1 = index($table->[2], " (");
193 1739         3458 my $p2 = index($table->[2], ")");
194 1739         5620 $table->[4] = substr($table->[2], $p1 + 2, $p2 - $p1 - 2);
195 1739         8082 $table->[2] = substr($table->[2], 0, $p1);
196             }
197 16161         68009 return $table;
198             }
199              
200             1;
201             __END__