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 92     92   106701 use v5.26;
  92         325  
3 92     92   532 use strict;
  92         205  
  92         3662  
4 92     92   529 use warnings;
  92         176  
  92         4884  
5 92     92   3296 use Sisimai::String;
  92         234  
  92         3095  
6 92     92   1697 use Sisimai::RFC791;
  92         186  
  92         135511  
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 23548     23548 1 329215 my $class = shift;
44 23548   100     71760 my $argv0 = shift || return 0;
45              
46             # Deal "localhost", "localhost6" as a valid hostname
47 21577 100 100     76496 return 1 if $argv0 eq 'localhost' || $argv0 eq 'localhost6';
48 21309 100 66     84558 return 0 if length $argv0 > 255 || length $argv0 < 4;
49 21303 100       51775 return 0 if index($argv0, ".") == -1;
50 20989 100       49885 return 0 if index($argv0, "..") > -1;
51 20687 100       37357 return 0 if index($argv0, "--") > -1;
52 20676 50       40312 return 0 if index($argv0, ".") == 0;
53 20676 50       38255 return 0 if index($argv0, "-") == 0;
54 20676 100       53723 return 0 if substr($argv0, -1, 1) eq "-";
55              
56 20675         107953 my @characters = split("", uc $argv0);
57 20675         35945 for my $e ( @characters ) {
58             # Check each characater is a number or an alphabet
59 304263         345958 my $f = ord $e;
60 304263 100       454132 return 0 if $f < 45; # 45 = '-'
61 304045 100       462337 return 0 if $f == 47; # 47 = '/'
62 303672 100 100     733637 return 0 if $f > 57 && $f < 65; # 57 = '9', 65 = 'A'
63 302586 100       503504 return 0 if $f > 90; # 90 = 'Z'
64             }
65              
66 18825         35820 my $p1 = rindex($argv0, ".");
67 18825 50       37068 my $cv = substr($argv0, $p1 + 1,); return 0 if length $cv > 63;
  18825         39479  
68 18825         56124 for my $e ( split("", $cv) ) {
69             # The top level domain should not include a number
70 46495 100 66     58971 my $f = ord $e; return 0 if $f > 47 && $f < 58;
  46495         152642  
71             }
72 17419         118797 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 10539     10539 1 20789 my $class = shift;
81 10539   100     30171 my $email = shift || return 0;
82              
83 10538         18373 $email =~ s/\A[<]//g; $email =~ s/[>]\z//g;
  10538         16539  
84 10538 100       21066 return 0 if length $email < 16; # e@[IPv4:0.0.0.0] is 16 characters
85 10157 100       35605 return 0 if substr($email, -1, 1) ne ']';
86              
87 13 100       24 my $lastb = rindex($email, '@[IPv'); return 0 if $lastb < 0;
  13         61  
88 12         46 my $dpart = [split('@', $email)]->[-1];
89              
90 12 100       56 if( index($email, '@[IPv4:') > 0 ) {
    100          
91             # neko@[IPv4:192.0.2.25]
92 4         11 my $ipv4a = substr($email, $lastb + 7,);
93 4         8 $ipv4a = substr($ipv4a, 0, length($ipv4a) - 1);
94 4         60 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     50 return 1 if length $dpart > 2 && rindex($dpart, ':') > 7;
116             }
117 2         8 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 1824     1824 0 18714 my $class = shift;
126 1824   100     8740 my $argv1 = shift || return "";
127              
128 1814         6679 my $sourcetext = lc $argv1;
129 1814         3699 my $sourcelist = [];
130 1814         4530 my $foundtoken = [];
131 1814         3192 my $thelongest = 0;
132 1814         3234 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 1814         17669 s/\[/ [/g, s/\(/ (/g, s/
138 1814         11201 s/\]/] /g, s/\)/) /g, s/ /g for $sourcetext; # Suffix a space character behind each bracket
139 1814         11215 s/:/: /g, s/;/; /g for $sourcetext; # Suffix a space character behind : and ;
140 1814         9981 $sourcetext = Sisimai::String->sweep($sourcetext);
141              
142 1814         3426 MAKELIST: while(1) {
143 1814         4338 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 17110 100       34210 next unless Sisimai::String->aligned(\$sourcetext, $e);
147              
148 199         667 my $p1 = index($sourcetext, $e->[0]);
149 199         600 my $p2 = index($sourcetext, $e->[1]);
150 199         455 my $cw = length $e->[0];
151 199 100       833 next if $p1 + $cw >= $p2;
152              
153 166         1622 $sourcelist = [split(" ", substr($sourcetext, $p1 + $cw, $p2 - $cw - $p1))];
154 166         776 last MAKELIST;
155             }
156              
157             # Check other patterns which are not sandwiched
158 1648         3814 for my $e ( @$StartAfter ) {
159             # $StartAfter have some strings, not an array.
160 6310 100       10486 my $p1 = index($sourcetext, $e); next if $p1 < 0;
  6310         14095  
161 101         231 my $cw = length $e;
162 101         530 $sourcelist = [split(" ", substr($sourcetext, $p1 + $cw,))];
163 101         350 last MAKELIST;
164             }
165              
166 1547         4006 for my $e ( @$ExistUntil ) {
167             # ExistUntil have some strings, not an array.
168 1547 100       3382 my $p1 = index($sourcetext, $e); next if $p1 < 0;
  1547         8348  
169 2         7 $sourcelist = [split(" ", substr($sourcetext, 0, $p1))];
170 2         4 last MAKELIST;
171             }
172              
173 1545 50       33171 $sourcelist = [split(" ", $sourcetext)] if scalar @$sourcelist == 0;
174 1545         7466 last MAKELIST;
175             }
176              
177 1814         3952 for my $e ( @$sourcelist ) {
178             # Pick some strings which is 4 or more length, is including "." character
179 49607 100       96649 substr($e, -1, 1, "") if substr($e, -1, 1) eq "."; # Remove "." at the end of the string
180 49607         73044 $e =~ y/[]()<>:;//d; # Remove brackets, colon, and semi-colon
181              
182 49607 100 100     138619 next if length $e < 4 || index($e, ".") < 0 || __PACKAGE__->is_internethost($e) == 0;
      100        
183 1140         3939 push @$foundtoken, $e;
184             }
185 1814 100       12111 return "" if scalar @$foundtoken == 0;
186 585 100       4439 return $foundtoken->[0] if scalar @$foundtoken == 1;
187              
188 161         607 for my $e ( @$foundtoken ) {
189             # Returns the longest hostname
190 716 100       1016 my $cw = length $e; next if $thelongest >= $cw;
  716         1495  
191 184         398 $hostnameis = $e;
192 184         342 $thelongest = $cw;
193             }
194 161         1869 return $hostnameis;
195             }
196              
197             1;
198             __END__