File Coverage

lib/Sisimai/RFC1123.pm
Criterion Covered Total %
statement 99 99 100.0
branch 56 60 93.3
condition 24 27 88.8
subroutine 8 8 100.0
pod 2 3 66.6
total 189 197 95.9


line stmt bran cond sub pod time code
1             package Sisimai::RFC1123;
2 94     94   85579 use v5.26;
  94         249  
3 94     94   385 use strict;
  94         172  
  94         2829  
4 94     94   362 use warnings;
  94         147  
  94         3805  
5 94     94   2418 use Sisimai::String;
  94         160  
  94         2076  
6 94     94   1405 use Sisimai::RFC791;
  94         135  
  94         95384  
7              
8             state $Sandwiched = [
9             # (Postfix) postfix/src/smtp/smtp_proto.c: "host %s said: %s (in reply to %s)",
10             # - : host re2.example.com[198.51.100.2] said: 550 ...
11             # - : host r2.example.org[198.51.100.18] refused to talk to me:
12             ["host ", " said: "],
13             ["host ", " talk to me: "],
14             ["while talking to ", ":"], # (Sendmail) ... while talking to mx.bouncehammer.jp.:
15             ["host ", " ["], # (Exim) host mx.example.jp [192.0.2.20]: 550 5.7.0
16             [" by ", ". ["], # (Gmail) ...for the recipient domain example.jp by mx.example.jp. [192.0.2.1].
17              
18             # (MailFoundry)
19             # - Delivery failed for the following reason: Server mx22.example.org[192.0.2.222] failed with: 550...
20             # - Delivery failed for the following reason: mail.example.org[192.0.2.222] responded with failure: 552..
21             ["delivery failed for the following reason: ", " with"],
22             ["remote system: ", "("], # (MessagingServer) Remote system: dns;mx.example.net (mx. --
23             ["smtp server <", ">"], # (X6) SMTP Server rejected recipient ...
24             ["-mta: ", ">"], # (MailMarshal) Reporting-MTA:
25             [" : ", "["], # (SendGrid) cat:000000: : 192.0.2.1 : mx.example.jp:[192.0.2.2]...
26             ];
27             state $StartAfter = [
28             "generating server: ", # (Exchange2007) en-US/Generating server: mta4.example.org
29             "serveur de g", # (Exchange2007) fr-FR/Serveur de gènèration
30             "server di generazione", # (Exchange2007) it-CH
31             "genererande server", # (Exchange2007) sv-SE
32             ];
33             state $ExistUntil = [
34             " did not like our ", # (Dragonfly) mail-inbound.libsisimai.net [192.0.2.25] did not like our DATA: ...
35             ];
36              
37             sub is_internethost {
38             # Check that the argument is a valid Internet hostname or not
39             # @param [String] argv0 String to be checked
40             # @return [Boolean] 0: is not a valid hostname
41             # 1: is a valid hostname
42             # @since v5.2.0
43 23726     23726 1 234578 my $class = shift;
44 23726   100     47388 my $argv0 = shift || return 0;
45              
46             # Deal "localhost", "localhost6" as a valid hostname
47 21735 100 100     49595 return 1 if $argv0 eq 'localhost' || $argv0 eq 'localhost6';
48 21467 100 66     45216 return 0 if length $argv0 > 255 || length $argv0 < 4;
49 21461 100       29124 return 0 if index($argv0, ".") == -1;
50 21147 100       28945 return 0 if index($argv0, "..") > -1;
51 20839 100       25480 return 0 if index($argv0, "--") > -1;
52 20828 50       27485 return 0 if index($argv0, ".") == 0;
53 20828 50       24832 return 0 if index($argv0, "-") == 0;
54 20828 100       31525 return 0 if substr($argv0, -1, 1) eq "-";
55              
56 20827         67158 my @characters = split("", uc $argv0);
57 20827         23808 for my $e ( @characters ) {
58             # Check each characater is a number or an alphabet
59 306202         223556 my $f = ord $e;
60 306202 100       290279 return 0 if $f < 45; # 45 = '-'
61 305984 100       291598 return 0 if $f == 47; # 47 = '/'
62 305611 100 100     455669 return 0 if $f > 57 && $f < 65; # 57 = '9', 65 = 'A'
63 304494 100       336683 return 0 if $f > 90; # 90 = 'Z'
64             }
65              
66 18946         20658 my $p1 = rindex($argv0, ".");
67 18946 50       26907 my $cv = substr($argv0, $p1 + 1,); return 0 if length $cv > 63;
  18946         24006  
68 18946         31247 for my $e ( split("", $cv) ) {
69             # The top level domain should not include a number
70 46751 100 66     36507 my $f = ord $e; return 0 if $f > 47 && $f < 58;
  46751         94219  
71             }
72 17514         66882 return 1;
73             }
74              
75             sub is_domainliteral {
76             # returns true if the domain part is [IPv4:...] or [IPv6:...].
77             # @param string email Email address.
78             # @return bool 0: the domain part is not a domain literal.
79             # 1: the domain part is a domain literal.
80 10609     10609 1 15580 my $class = shift;
81 10609   100     13923 my $email = shift || return 0;
82              
83 10608         11476 $email =~ s/\A[<]//g; $email =~ s/[>]\z//g;
  10608         12169  
84 10608 100       15389 return 0 if length $email < 16; # e@[IPv4:0.0.0.0] is 16 characters
85 10227 100       25339 return 0 if substr($email, -1, 1) ne ']';
86              
87 13 100       22 my $lastb = rindex($email, '@[IPv'); return 0 if $lastb < 0;
  13         28  
88 12         43 my $dpart = [split('@', $email)]->[-1];
89              
90 12 100       47 if( index($email, '@[IPv4:') > 0 ) {
    100          
91             # neko@[IPv4:192.0.2.25]
92 4         11 my $ipv4a = substr($email, $lastb + 7,);
93 4         7 $ipv4a = substr($ipv4a, 0, length($ipv4a) - 1);
94 4         25 return Sisimai::RFC791->is_ipv4address($ipv4a);
95              
96             } elsif( index($email, '@[IPv6:') > 0 ) {
97             # neko@[IPv6:2001:0DB8:0000:0000:0000:0000:0000:0001]
98             # neko@[IPv6:2001:0DB8:0000:0000:0000:0000:0000:0001]
99             # IPv6-address-literal = "IPv6:" IPv6-addr
100             # IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
101             # IPv6-hex = 1*4HEXDIG
102             # IPv6-full = IPv6-hex 7(":" IPv6-hex)
103             # IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
104             # [IPv6-hex *5(":" IPv6-hex)]
105             # ; The "::" represents at least 2 16-bit groups of
106             # ; zeros. No more than 6 groups in addition to the
107             # ; "::" may be present.
108             # IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
109             # IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
110             # [IPv6-hex *3(":" IPv6-hex) ":"]
111             # IPv4-address-literal
112             # ; The "::" represents at least 2 16-bit groups of
113             # ; zeros. No more than 4 groups in addition to the
114             # ; "::" and IPv4-address-literal may be present.
115 7 100 66     41 return 1 if length $dpart > 2 && rindex($dpart, ':') > 7;
116             }
117 2         9 return 0
118             }
119              
120             sub find {
121             # find() returns a valid internet hostname found from the argument
122             # @param string argv1 String including hostnames
123             # @return string A valid internet hostname found in the argument
124             # @since v5.2.0
125 1844     1844 0 11413 my $class = shift;
126 1844   100     6071 my $argv1 = shift || return "";
127              
128 1834         4135 my $sourcetext = lc $argv1;
129 1834         2253 my $sourcelist = [];
130 1834         2316 my $foundtoken = [];
131 1834         2122 my $thelongest = 0;
132 1834         2144 my $hostnameis = "";
133              
134             # Replace some string for splitting by " "
135             # - mx.example.net[192.0.2.1] => mx.example.net [192.0.2.1]
136             # - mx.example.jp:[192.0.2.1] => mx.example.jp :[192.0.2.1]
137 1834         10859 s/\[/ [/g, s/\(/ (/g, s/
138 1834         6910 s/\]/] /g, s/\)/) /g, s/ /g for $sourcetext; # Suffix a space character behind each bracket
139 1834         7278 s/:/: /g, s/;/; /g for $sourcetext; # Suffix a space character behind : and ;
140 1834         6112 $sourcetext = Sisimai::String->sweep($sourcetext);
141              
142 1834         2449 MAKELIST: while(1) {
143 1834         3488 for my $e ( @$Sandwiched ) {
144             # Check a hostname exists between the $e->[0] and $e->[1] at array "Sandwiched"
145             # Each array in Sandwiched have 2 elements
146 17310 100       24342 next unless Sisimai::String->aligned(\$sourcetext, $e);
147              
148 199         391 my $p1 = index($sourcetext, $e->[0]);
149 199         347 my $p2 = index($sourcetext, $e->[1]);
150 199         262 my $cw = length $e->[0];
151 199 100       557 next if $p1 + $cw >= $p2;
152              
153 166         1085 $sourcelist = [split(" ", substr($sourcetext, $p1 + $cw, $p2 - $cw - $p1))];
154 166         394 last MAKELIST;
155             }
156              
157             # Check other patterns which are not sandwiched
158 1668         2676 for my $e ( @$StartAfter ) {
159             # $StartAfter have some strings, not an array.
160 6390 100       7941 my $p1 = index($sourcetext, $e); next if $p1 < 0;
  6390         9369  
161 101         122 my $cw = length $e;
162 101         331 $sourcelist = [split(" ", substr($sourcetext, $p1 + $cw,))];
163 101         179 last MAKELIST;
164             }
165              
166 1567         2095 for my $e ( @$ExistUntil ) {
167             # ExistUntil have some strings, not an array.
168 1567 100       2151 my $p1 = index($sourcetext, $e); next if $p1 < 0;
  1567         2954  
169 2         6 $sourcelist = [split(" ", substr($sourcetext, 0, $p1))];
170 2         4 last MAKELIST;
171             }
172              
173 1565 50       22190 $sourcelist = [split(" ", $sourcetext)] if scalar @$sourcelist == 0;
174 1565         4970 last MAKELIST;
175             }
176              
177 1834         2301 for my $e ( @$sourcelist ) {
178             # Pick some strings which is 4 or more length, is including "." character
179 49975 100       59279 substr($e, -1, 1, "") if substr($e, -1, 1) eq "."; # Remove "." at the end of the string
180 49975         41500 $e =~ y/[]()<>:;//d; # Remove brackets, colon, and semi-colon
181              
182 49975 100 100     89416 next if length $e < 4 || index($e, ".") < 0 || __PACKAGE__->is_internethost($e) == 0;
      100        
183 1140         2256 push @$foundtoken, $e;
184             }
185 1834 100       6718 return "" if scalar @$foundtoken == 0;
186 585 100       2539 return $foundtoken->[0] if scalar @$foundtoken == 1;
187              
188 161         296 for my $e ( @$foundtoken ) {
189             # Returns the longest hostname
190 716 100       581 my $cw = length $e; next if $thelongest >= $cw;
  716         1090  
191 184         220 $hostnameis = $e;
192 184         202 $thelongest = $cw;
193             }
194 161         1272 return $hostnameis;
195             }
196              
197             1;
198             __END__