sisimai 5.2.1-java → 5.4.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rake-test.yml +1 -1
- data/ChangeLog.md +24 -0
- data/Makefile +3 -2
- data/README-JA.md +4 -4
- data/README.md +8 -8
- data/lib/sisimai/address.rb +45 -56
- data/lib/sisimai/arf.rb +11 -16
- data/lib/sisimai/datetime.rb +16 -50
- data/lib/sisimai/fact/json.rb +5 -5
- data/lib/sisimai/fact/yaml.rb +3 -3
- data/lib/sisimai/fact.rb +21 -12
- data/lib/sisimai/lda.rb +3 -3
- data/lib/sisimai/lhost/activehunter.rb +4 -6
- data/lib/sisimai/lhost/amazonses.rb +5 -6
- data/lib/sisimai/lhost/apachejames.rb +7 -9
- data/lib/sisimai/lhost/biglobe.rb +3 -5
- data/lib/sisimai/lhost/courier.rb +4 -6
- data/lib/sisimai/lhost/domino.rb +4 -5
- data/lib/sisimai/lhost/dragonfly.rb +3 -5
- data/lib/sisimai/lhost/einsundeins.rb +6 -8
- data/lib/sisimai/lhost/exchange2003.rb +10 -12
- data/lib/sisimai/lhost/exchange2007.rb +4 -5
- data/lib/sisimai/lhost/exim.rb +6 -8
- data/lib/sisimai/lhost/ezweb.rb +10 -12
- data/lib/sisimai/lhost/fml.rb +2 -3
- data/lib/sisimai/lhost/gmail.rb +4 -6
- data/lib/sisimai/lhost/gmx.rb +6 -8
- data/lib/sisimai/lhost/googlegroups.rb +1 -2
- data/lib/sisimai/lhost/googleworkspace.rb +3 -4
- data/lib/sisimai/lhost/imailserver.rb +6 -7
- data/lib/sisimai/lhost/interscanmss.rb +1 -2
- data/lib/sisimai/lhost/kddi.rb +5 -8
- data/lib/sisimai/lhost/mailfoundry.rb +4 -7
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +4 -6
- data/lib/sisimai/lhost/messagingserver.rb +5 -7
- data/lib/sisimai/lhost/mfilter.rb +4 -6
- data/lib/sisimai/lhost/notes.rb +7 -9
- data/lib/sisimai/lhost/opensmtpd.rb +2 -4
- data/lib/sisimai/lhost/postfix.rb +8 -11
- data/lib/sisimai/lhost/qmail.rb +5 -8
- data/lib/sisimai/lhost/sendmail.rb +7 -10
- data/lib/sisimai/lhost/v5sendmail.rb +15 -17
- data/lib/sisimai/lhost/verizon.rb +9 -14
- data/lib/sisimai/lhost/x1.rb +4 -6
- data/lib/sisimai/lhost/x2.rb +5 -7
- data/lib/sisimai/lhost/x3.rb +3 -4
- data/lib/sisimai/lhost/x6.rb +4 -6
- data/lib/sisimai/lhost/zoho.rb +6 -8
- data/lib/sisimai/lhost.rb +1 -1
- data/lib/sisimai/mail/mbox.rb +1 -1
- data/lib/sisimai/mail/memory.rb +1 -1
- data/lib/sisimai/mail.rb +8 -8
- data/lib/sisimai/message.rb +11 -13
- data/lib/sisimai/order.rb +12 -11
- data/lib/sisimai/reason/authfailure.rb +10 -10
- data/lib/sisimai/reason/badreputation.rb +4 -6
- data/lib/sisimai/reason/blocked.rb +6 -8
- data/lib/sisimai/reason/contenterror.rb +5 -6
- data/lib/sisimai/reason/delivered.rb +2 -2
- data/lib/sisimai/reason/exceedlimit.rb +7 -8
- data/lib/sisimai/reason/expired.rb +6 -7
- data/lib/sisimai/reason/failedstarttls.rb +5 -7
- data/lib/sisimai/reason/feedback.rb +2 -2
- data/lib/sisimai/reason/filtered.rb +7 -10
- data/lib/sisimai/reason/hasmoved.rb +4 -5
- data/lib/sisimai/reason/hostunknown.rb +6 -7
- data/lib/sisimai/reason/mailboxfull.rb +7 -8
- data/lib/sisimai/reason/mailererror.rb +5 -8
- data/lib/sisimai/reason/mesgtoobig.rb +5 -6
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +4 -5
- data/lib/sisimai/reason/notaccept.rb +5 -8
- data/lib/sisimai/reason/notcompliantrfc.rb +5 -6
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +6 -9
- data/lib/sisimai/reason/rejected.rb +5 -6
- data/lib/sisimai/reason/requireptr.rb +6 -7
- data/lib/sisimai/reason/securityerror.rb +6 -9
- data/lib/sisimai/reason/spamdetected.rb +8 -9
- data/lib/sisimai/reason/speeding.rb +6 -7
- data/lib/sisimai/reason/suppressed.rb +3 -7
- data/lib/sisimai/reason/suspend.rb +5 -7
- data/lib/sisimai/reason/syntaxerror.rb +3 -5
- data/lib/sisimai/reason/systemerror.rb +6 -9
- data/lib/sisimai/reason/systemfull.rb +5 -8
- data/lib/sisimai/reason/toomanyconn.rb +5 -6
- data/lib/sisimai/reason/undefined.rb +2 -2
- data/lib/sisimai/reason/userunknown.rb +8 -9
- data/lib/sisimai/reason/vacation.rb +4 -5
- data/lib/sisimai/reason/virusdetected.rb +4 -5
- data/lib/sisimai/reason.rb +13 -13
- data/lib/sisimai/rfc1123.rb +4 -8
- data/lib/sisimai/rfc1894.rb +5 -6
- data/lib/sisimai/rfc2045.rb +27 -31
- data/lib/sisimai/rfc3464/thirdparty.rb +1 -1
- data/lib/sisimai/rfc3464.rb +7 -9
- data/lib/sisimai/rfc3834.rb +5 -9
- data/lib/sisimai/rfc5322.rb +8 -26
- data/lib/sisimai/rfc791.rb +6 -4
- data/lib/sisimai/rhost/google.rb +8 -0
- data/lib/sisimai/rhost/microsoft.rb +17 -5
- data/lib/sisimai/rhost.rb +2 -2
- data/lib/sisimai/smtp/command.rb +1 -1
- data/lib/sisimai/smtp/failure.rb +5 -12
- data/lib/sisimai/smtp/reply.rb +33 -12
- data/lib/sisimai/smtp/status.rb +21 -22
- data/lib/sisimai/smtp/transcript.rb +1 -10
- data/lib/sisimai/string.rb +20 -30
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +11 -11
- data/set-of-emails/maildir/bsd/rhost-microsoft-06.eml +45 -0
- metadata +8 -10
data/lib/sisimai/rfc791.rb
CHANGED
@@ -18,6 +18,7 @@ module Sisimai
|
|
18
18
|
end
|
19
19
|
return true
|
20
20
|
end
|
21
|
+
|
21
22
|
# Find an IPv4 address from the given string
|
22
23
|
# @param [String] argv1 String including an IPv4 address
|
23
24
|
# @return [Array] List of IPv4 addresses
|
@@ -26,14 +27,15 @@ module Sisimai
|
|
26
27
|
return nil if argv0.to_s.empty?
|
27
28
|
return [] if argv0.size < 7
|
28
29
|
|
30
|
+
given = argv0.dup
|
29
31
|
ipv4a = []
|
30
32
|
%w|( ) [ ] ,|.each do |e|
|
31
33
|
# Rewrite: "mx.example.jp[192.0.2.1]" => "mx.example.jp 192.0.2.1"
|
32
|
-
p0 =
|
33
|
-
|
34
|
+
p0 = given.index(e); next unless p0
|
35
|
+
given[p0, 1] = ' '
|
34
36
|
end
|
35
37
|
|
36
|
-
|
38
|
+
given.split(' ').each do |e|
|
37
39
|
# Find string including an IPv4 address
|
38
40
|
next unless e.index('.') # IPv4 address must include "." character
|
39
41
|
|
@@ -55,7 +57,7 @@ module Sisimai
|
|
55
57
|
eo = ''
|
56
58
|
next
|
57
59
|
end
|
58
|
-
eo
|
60
|
+
eo += as.chr
|
59
61
|
break if eo.to_i > 255
|
60
62
|
end
|
61
63
|
ipv4a << e if eo.size > 0 && eo.to_i < 256
|
data/lib/sisimai/rhost/google.rb
CHANGED
@@ -82,7 +82,15 @@ module Sisimai
|
|
82
82
|
# - 421 4.7.32 Your email has been rate limited because the From: header (RFC5322) in
|
83
83
|
# this message isn't aligned with either the authenticated SPF or DKIM organizational
|
84
84
|
# domain.
|
85
|
+
# - 421 5.7.32 Your email was blocked because the From: header (RFC5322) in this message
|
86
|
+
# isn't aligned with either the authenticated SPF or DKIM organizational domain.
|
85
87
|
['421', '4.7.32', 'aligned with either the authenticated spf or dkim'],
|
88
|
+
["421", "5.7.32", "aligned with either the authenticated spf or dkim"],
|
89
|
+
|
90
|
+
# - 421 4.7.40 Your email has been rate limited because the sending domain doesn't
|
91
|
+
# have a DMARC record, or the DMARC record doesn’t specify a DMARC policy. Gmail
|
92
|
+
# requires all bulk email senders to add a DMARC record to their sending domain.
|
93
|
+
["421", "4.7.40", "to add a dmarc record to "],
|
86
94
|
],
|
87
95
|
'badreputation' => [
|
88
96
|
# - 421 4.7.0 This message is suspicious due to the very low reputation of the sending
|
@@ -23,6 +23,13 @@ module Sisimai
|
|
23
23
|
# - Access denied, sending domain [$SenderDomain] does not pass DMARC verification
|
24
24
|
# - The sender's domain in the 5322.From address doesn't pass DMARC.
|
25
25
|
['5.7.509', 0, 0, 'does not pass dmarc verification'],
|
26
|
+
|
27
|
+
# - 550 5.7.515 Access denied, sending domain EXAMPLE.JP doesn't meet the required
|
28
|
+
# authentication level. The sender's domain in the 5322.From address doesn't meet
|
29
|
+
# the authentication requirements defined for the sender. To learn how to fix this
|
30
|
+
# see: https://go.microsoft.com/fwlink/p/?linkid=2319303
|
31
|
+
# Spf= Fail , Dkim= Pass , DMARC= Pass ...
|
32
|
+
["5.7.515", 0, 0, "doesn't meet the required authentication level"],
|
26
33
|
],
|
27
34
|
'badreputation' => [
|
28
35
|
# Undocumented error messages ---------------------------------------------------------
|
@@ -597,11 +604,16 @@ module Sisimai
|
|
597
604
|
['5.2.14', 0, 0, 'misconfigured forwarding address'],
|
598
605
|
|
599
606
|
# Undocumented error messages ---------------------------------------------------------
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
607
|
+
# - 451 4.4.22 Message failed to be replicated: no healthy peers found ... (in reply to end of DATA command)
|
608
|
+
# - 451 4.4.23 Message failed to be replicated: No healthy secondary server available
|
609
|
+
# to accept replica at this time. ... (in reply to end of DATA command)
|
610
|
+
# - 451 4.4.28 Message failed to be replicated:
|
611
|
+
# Microsoft.Exchange.Transport.Net.Http.TransportHttpException(session Id: -1) ...(in reply to end of DATA command)
|
612
|
+
# - 451 4.4.28 Message failed to be replicated:
|
613
|
+
# System.Net.Http.HttpRequestException(session Id: ****) ... (in reply to end of DATA command)
|
614
|
+
["4.4.", "22", "28", "message failed to be replicated:"],
|
615
|
+
["4.4.3", "", "", "temporary server error. please try again later attr18"],
|
616
|
+
["4.7.0", "", "", "temporary server error. please try again later. prx4 nexthop:"],
|
605
617
|
|
606
618
|
# 550 5.4.318 Message expired, connection reset (SuspiciousRemoteServerError)
|
607
619
|
# 450 4.4.318 Connection was closed abruptly (SuspiciousRemoteServerError)
|
data/lib/sisimai/rhost.rb
CHANGED
@@ -74,8 +74,8 @@ module Sisimai
|
|
74
74
|
return "" if argvs.nil?
|
75
75
|
|
76
76
|
rhostclass = name(argvs); return "" if rhostclass.empty?
|
77
|
-
modulepath = "sisimai/rhost
|
78
|
-
modulename = "Sisimai::Rhost
|
77
|
+
modulepath = "sisimai/rhost/#{rhostclass.downcase}"; require modulepath
|
78
|
+
modulename = "Sisimai::Rhost::#{rhostclass}"
|
79
79
|
|
80
80
|
#rhostclass = "sisimai/rhost/" << modulename.downcase.split("::")[2]; require rhostclass
|
81
81
|
reasontext = Module.const_get(modulename).find(argvs)
|
data/lib/sisimai/smtp/command.rb
CHANGED
@@ -31,7 +31,7 @@ module Sisimai
|
|
31
31
|
return "" unless Sisimai::SMTP::Command.test(argv0)
|
32
32
|
|
33
33
|
issuedcode = " " + argv0.downcase + " "
|
34
|
-
commandmap = {
|
34
|
+
commandmap = {"STAR" => "STARTTLS", "XFOR" => "XFORWARD"}
|
35
35
|
commandset = []
|
36
36
|
|
37
37
|
Detectable.each do |e|
|
data/lib/sisimai/smtp/failure.rb
CHANGED
@@ -12,8 +12,7 @@ module Sisimai
|
|
12
12
|
# false: Is not a permanent error
|
13
13
|
# @since v4.17.3
|
14
14
|
def is_permanent(argv1 = '')
|
15
|
-
return false
|
16
|
-
return false unless argv1.size > 0
|
15
|
+
return false if argv1.to_s == ""
|
17
16
|
|
18
17
|
statuscode = Sisimai::SMTP::Status.find(argv1)
|
19
18
|
statuscode = Sisimai::SMTP::Reply.find(argv1) if statuscode.empty?
|
@@ -28,16 +27,14 @@ module Sisimai
|
|
28
27
|
# false: is not a temporary error
|
29
28
|
# @since v5.2.0
|
30
29
|
def is_temporary(argv1 = '')
|
31
|
-
return false
|
32
|
-
return false unless argv1.size > 0
|
30
|
+
return false if argv1.to_s == ""
|
33
31
|
|
34
32
|
statuscode = Sisimai::SMTP::Status.find(argv1);
|
35
33
|
statuscode = Sisimai::SMTP::Reply.find(argv1) if statuscode.empty?
|
36
34
|
issuedcode = argv1.downcase
|
37
35
|
|
38
36
|
return true if statuscode[0, 1] == "4"
|
39
|
-
return true if issuedcode.include?(' temporar')
|
40
|
-
return true if issuedcode.include?(' persistent')
|
37
|
+
return true if issuedcode.include?(' temporar') || issuedcode.include?(' persistent')
|
41
38
|
return false
|
42
39
|
end
|
43
40
|
|
@@ -46,9 +43,7 @@ module Sisimai
|
|
46
43
|
# @param [String] argv2 String including SMTP Status code
|
47
44
|
# @return [Boolean] true: is a hard bounce
|
48
45
|
def is_hardbounce(argv1 = '', argv2 = '')
|
49
|
-
return false
|
50
|
-
return false unless argv1.size > 0
|
51
|
-
|
46
|
+
return false if argv1.to_s == ""
|
52
47
|
return false if argv1 == "undefined" || argv1 == "onhold"
|
53
48
|
return false if argv1 == "delivered" || argv1 == "feedback" || argv1 == "vacation"
|
54
49
|
return true if argv1 == "hasmoved" || argv1 == "userunknown" || argv1 == "hostunknown"
|
@@ -76,9 +71,7 @@ module Sisimai
|
|
76
71
|
# @param [String] argv2 String including SMTP Status code
|
77
72
|
# @return [Boolean] true: is a soft bounce
|
78
73
|
def is_softbounce(argv1 = '', argv2 = '')
|
79
|
-
return false
|
80
|
-
return false unless argv1.size > 0
|
81
|
-
|
74
|
+
return false if argv1.to_s == ""
|
82
75
|
return false if argv1 == "delivered" || argv1 == "feedback" || argv1 == "vacation"
|
83
76
|
return false if argv1 == "hasmoved" || argv1 == "userunknown" || argv1 == "hostunknown"
|
84
77
|
return true if argv1 == "undefined" || argv1 == "onhold"
|
data/lib/sisimai/smtp/reply.rb
CHANGED
@@ -58,8 +58,11 @@ module Sisimai
|
|
58
58
|
# 251 User not local; will forward to <forward-path> (See Section 3.4)
|
59
59
|
# 252 Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3)
|
60
60
|
# 253 OK, <n> pending messages for node <domain> started (See RFC1985)
|
61
|
+
# 334 A server challenge is sent as a 334 reply with the text part containing the [BASE64]
|
62
|
+
# encoded string supplied by the SASL mechanism. This challenge MUST NOT contain any
|
63
|
+
# text other than the BASE64 encoded challenge. (RFC4954)
|
61
64
|
# 354 Start mail input; end with <CRLF>.<CRLF>
|
62
|
-
'211', '214', '220', '221', '235', '250', '251', '252', '253', '354'
|
65
|
+
'211', '214', '220', '221', '235', '250', '251', '252', '253', '334', '354'
|
63
66
|
].freeze
|
64
67
|
ReplyCode4 = [
|
65
68
|
# 421 <domain> Service not available, closing transmission channel (This may be a reply
|
@@ -86,7 +89,6 @@ module Sisimai
|
|
86
89
|
# 502 Command not implemented (see Section 4.2.4)
|
87
90
|
# 503 Bad sequence of commands
|
88
91
|
# 504 Command parameter not implemented
|
89
|
-
# 520 Please use the correct QHLO ID (See https://datatracker.ietf.org/doc/id/draft-fanf-smtp-quickstart-01.txt)
|
90
92
|
# 521 Host does not accept mail (See RFC7504)
|
91
93
|
# 523 Encryption Needed (See RFC5248)
|
92
94
|
# 524 (See RFC5248)
|
@@ -104,11 +106,27 @@ module Sisimai
|
|
104
106
|
# 554 Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here")
|
105
107
|
# 555 MAIL FROM/RCPT TO parameters not recognized or not implemented
|
106
108
|
# 556 Domain does not accept mail (See RFC7504)
|
107
|
-
|
108
|
-
|
109
|
-
'535', '538', '551', '555', '556', '554', '557', '500', '501', '502', '503', '504',
|
109
|
+
"550", "552", "553", "551", "521", "525", "523", "524", "530", "533", "534", "535", "538",
|
110
|
+
"555", "556", "554", "500", "501", "502", "503", "504",
|
110
111
|
].freeze
|
111
|
-
CodeOfSMTP = {
|
112
|
+
CodeOfSMTP = {'2' => ReplyCode2, '4' => ReplyCode4, '5' => ReplyCode5}.freeze
|
113
|
+
Associated = {
|
114
|
+
"422" => ["AUTH", "4.7.12", "securityerror"], # RFC5238
|
115
|
+
"432" => ["AUTH", "4.7.12", "securityerror"], # RFC4954, RFC5321
|
116
|
+
"500" => ["", "", "syntaxerror"], # RFC5321
|
117
|
+
"501" => ["", "", "syntaxerror"], # RFC5321
|
118
|
+
"502" => ["", "", "syntaxerror"], # RFC5321
|
119
|
+
"503" => ["", "", "syntaxerror"], # RFC5321
|
120
|
+
"504" => ["", "", "syntaxerror"], # RFC5321
|
121
|
+
"521" => ["CONN", "", "notaccept"], # RFC7504
|
122
|
+
"523" => ["AUTH", "", "securityerror"], # RFC5248
|
123
|
+
"524" => ["AUTH", "", "securityerror"], # RFC5248
|
124
|
+
"525" => ["AUTH", "", "securityerror"], # RFC5248
|
125
|
+
"534" => ["AUTH", "5.7.9", "securityerror"], # RFC4954, RFC5248
|
126
|
+
"535" => ["AUTH", "5.7.8", "securityerror"], # RFC4954, RFC5248
|
127
|
+
"538" => ["AUTH", "5.7.11", "securityerror"], # RFC4954, RFC5248
|
128
|
+
"556" => ["RCPT", "", "notaccept"], # RFC7504
|
129
|
+
}.freeze
|
112
130
|
|
113
131
|
# Check whether a reply code is a valid code or not
|
114
132
|
# @param [String] argv1 Reply Code(DSN)
|
@@ -122,7 +140,7 @@ module Sisimai
|
|
122
140
|
first = (reply / 100).to_i
|
123
141
|
|
124
142
|
return false if reply < 211
|
125
|
-
return false if reply >
|
143
|
+
return false if reply > 556
|
126
144
|
return false if reply % 100 > 59
|
127
145
|
|
128
146
|
if first == 2
|
@@ -135,7 +153,7 @@ module Sisimai
|
|
135
153
|
|
136
154
|
if first == 3
|
137
155
|
# 3yz
|
138
|
-
return false unless reply == 354
|
156
|
+
return false unless reply == 334 || reply == 354
|
139
157
|
return true
|
140
158
|
end
|
141
159
|
|
@@ -145,11 +163,9 @@ module Sisimai
|
|
145
163
|
# Get SMTP Reply Code from the given string
|
146
164
|
# @param [String] argv1 String including SMTP Reply Code like 550
|
147
165
|
# @param [String] argv2 Status code like 5.1.1 or 2 or 4 or 5
|
148
|
-
# @return [String] SMTP Reply Code
|
149
|
-
# [Nil] The first argument did not include SMTP Reply Code value
|
166
|
+
# @return [String] SMTP Reply Code or an empty string
|
150
167
|
def find(argv1 = '', argv2 = '0')
|
151
|
-
return "" if argv1.to_s.size < 3
|
152
|
-
return "" if argv1.upcase.include?('X-UNIX')
|
168
|
+
return "" if argv1.to_s.size < 3 || argv1.upcase.include?('X-UNIX')
|
153
169
|
|
154
170
|
esmtperror = ' ' + argv1 + ' '
|
155
171
|
esmtpreply = ''
|
@@ -182,6 +198,11 @@ module Sisimai
|
|
182
198
|
return esmtpreply
|
183
199
|
end
|
184
200
|
|
201
|
+
# associatedwith returns a slice associated with the SMTP reply code of the argument
|
202
|
+
# @param [String] argv1 SMTP reply code like 550
|
203
|
+
# @return [Array] ["SMTP Command", "DSN", "Reason"]
|
204
|
+
# @since v5.2.2
|
205
|
+
def associatedwith(argv1 = ''); return Associated[argv1] || []; end
|
185
206
|
end
|
186
207
|
end
|
187
208
|
end
|
data/lib/sisimai/smtp/status.rb
CHANGED
@@ -677,11 +677,10 @@ module Sisimai
|
|
677
677
|
# @param [String] argv1 Reason name
|
678
678
|
# @param [True,False] argv2 false: Permanent error
|
679
679
|
# true: Temporary error
|
680
|
-
# @return [String
|
680
|
+
# @return [String] DSN or an empty string if the 1st argument is missing
|
681
681
|
# @see name
|
682
682
|
def code(argv1 = nil, argv2 = false)
|
683
|
-
return
|
684
|
-
return nil if argv1.empty?
|
683
|
+
return "" if argv1.to_s.empty?
|
685
684
|
|
686
685
|
table = argv2 ? InternalCode[:temporary] : InternalCode[:permanent]
|
687
686
|
code0 = table[argv1] || InternalCode[:permanent][argv1] || nil
|
@@ -690,13 +689,11 @@ module Sisimai
|
|
690
689
|
|
691
690
|
# Convert from the status code to the reason string
|
692
691
|
# @param [String] argv1 Status code(DSN)
|
693
|
-
# @return [String] Reason name
|
694
|
-
# [Nil] The first argument did not match with values in reason list
|
692
|
+
# @return [String] Reason name or an empty string
|
695
693
|
# @see code
|
696
694
|
def name(argv1 = nil)
|
697
|
-
return
|
698
|
-
return
|
699
|
-
return StandardCode[argv1] || nil
|
695
|
+
return "" unless Sisimai::SMTP::Status.test(argv1.to_s)
|
696
|
+
return StandardCode[argv1] || ""
|
700
697
|
end
|
701
698
|
|
702
699
|
# Check whether a status code is a valid code or not
|
@@ -705,29 +702,23 @@ module Sisimai
|
|
705
702
|
# @see code
|
706
703
|
# @since v5.0.0
|
707
704
|
def test(argv1 = '')
|
708
|
-
return false if argv1.to_s.empty?
|
709
|
-
return false if argv1.size < 5
|
710
|
-
return false if argv1.size > 7
|
705
|
+
return false if argv1.to_s.empty? || argv1.size < 5 || argv1.size > 7
|
711
706
|
|
712
707
|
token = []
|
713
708
|
argv1.split('.').each { |e| token << e.to_i }
|
714
709
|
return false unless token.size == 3
|
715
|
-
return false if token[0]
|
716
|
-
return false if token[0]
|
717
|
-
return false if token[
|
718
|
-
return false if token[1] < 0
|
719
|
-
return false if token[1] > 7
|
720
|
-
return false if token[2] < 0
|
710
|
+
return false if token[0] < 2 || token[0] == 3 || token[0] > 5
|
711
|
+
return false if token[1] < 0 || token[1] > 7
|
712
|
+
return false if token[2] < 0
|
721
713
|
return true
|
722
714
|
end
|
723
715
|
|
724
716
|
# Get a DSN code value from given string including DSN
|
725
717
|
# @param [String] argv1 String including DSN
|
726
718
|
# @param [String] argv2 An SMTP Reply Code or 2 or 4 or 5
|
727
|
-
# @return [String
|
719
|
+
# @return [String] An SMTP Status Code or an empty string
|
728
720
|
def find(argv1 = nil, argv2 = '0')
|
729
|
-
return "" if argv1.to_s.empty?
|
730
|
-
return "" if argv1.size < 7
|
721
|
+
return "" if argv1.to_s.empty? || argv1.size < 7
|
731
722
|
|
732
723
|
givenclass = argv2[0, 1]
|
733
724
|
eestatuses = if givenclass == '2' || givenclass == '4' || givenclass == '5'
|
@@ -856,8 +847,8 @@ module Sisimai
|
|
856
847
|
end
|
857
848
|
return statuscode if statuscode == codeinmesg
|
858
849
|
|
859
|
-
zeroindex1 = {
|
860
|
-
zeroindex2 = {
|
850
|
+
zeroindex1 = {'field' => statuscode.index('.0') || -1, 'error' => codeinmesg.index('.0') || -1}
|
851
|
+
zeroindex2 = {'field' => statuscode.index('.0.0') || -1, 'error' => codeinmesg.index('.0.0') || -1}
|
861
852
|
|
862
853
|
if zeroindex2['field'] > 0
|
863
854
|
# "Status:" field is "X.0.0"
|
@@ -892,6 +883,14 @@ module Sisimai
|
|
892
883
|
return statuscode
|
893
884
|
end
|
894
885
|
|
886
|
+
# is_explicit() returns 0 when the argument is empty or is an internal code
|
887
|
+
# @param string argv1 Status code
|
888
|
+
# @return bool false: The delivery status code is not explicit
|
889
|
+
def is_explicit(argv1 = '')
|
890
|
+
return false if argv1.nil? || argv1.empty?
|
891
|
+
return false if argv1.size == 7 && argv1.start_with?("5.0.9", "4.0.9")
|
892
|
+
return true
|
893
|
+
end
|
895
894
|
end
|
896
895
|
end
|
897
896
|
end
|
@@ -97,10 +97,8 @@ module Sisimai
|
|
97
97
|
else
|
98
98
|
# SMTP server sent a response "<<< response text"
|
99
99
|
p = e.index('<<< '); next unless p == 0
|
100
|
+
e = e[4, e.size].sub(/\A<<<[ ]/, '')
|
100
101
|
|
101
|
-
e = e[4, e.size]
|
102
|
-
|
103
|
-
e.sub!(/\A<<<[ ]/, '')
|
104
102
|
if cv = e.match(/\A([2-5]\d\d)[ ]/) then cx['response']['reply'] = cv[1] end
|
105
103
|
if cv = e.match(/\A[245]\d\d[ ]([245][.]\d{1,3}[.]\d{1,3})[ ]/) then cx['response']['status'] = cv[1] end
|
106
104
|
cx['response']['text'] << e
|
@@ -110,13 +108,6 @@ module Sisimai
|
|
110
108
|
return nil if esmtp.size == 0
|
111
109
|
return esmtp
|
112
110
|
end
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
111
|
end
|
121
112
|
end
|
122
113
|
end
|
data/lib/sisimai/string.rb
CHANGED
@@ -11,31 +11,26 @@ module Sisimai
|
|
11
11
|
# @param [String] addr1 Sender address
|
12
12
|
# @param [String] addr2 Recipient address
|
13
13
|
# @param [Integer] epoch Machine time of the email bounce
|
14
|
-
# @return [String] Message token(MD5 hex digest)
|
15
|
-
# @return [String] Blank/failed to create token
|
14
|
+
# @return [String] Message token(MD5 hex digest) or blank(failed to create token)
|
16
15
|
# @see http://en.wikipedia.org/wiki/ASCII
|
17
16
|
def token(addr1, addr2, epoch)
|
18
|
-
return
|
19
|
-
return
|
20
|
-
return
|
21
|
-
return
|
22
|
-
return nil if addr2.empty?
|
17
|
+
return "" unless addr1.is_a?(::String)
|
18
|
+
return "" unless addr2.is_a?(::String)
|
19
|
+
return "" unless epoch.is_a?(Integer)
|
20
|
+
return "" if addr1.empty? || addr2.empty?
|
23
21
|
|
24
22
|
# Format: STX(0x02) Sender-Address RS(0x1e) Recipient-Address ETX(0x03)
|
25
23
|
require 'digest/sha1'
|
26
|
-
return Digest::SHA1.hexdigest(
|
27
|
-
sprintf("\x02%s\x1e%s\x1e%d\x03", addr1.downcase, addr2.downcase, epoch)
|
28
|
-
)
|
24
|
+
return Digest::SHA1.hexdigest(sprintf("\x02%s\x1e%s\x1e%d\x03", addr1.downcase, addr2.downcase, epoch))
|
29
25
|
end
|
30
26
|
|
31
27
|
# The argument is 8-bit text or not
|
32
28
|
# @param [String] argvs Any string to be checked
|
33
|
-
# @return [
|
34
|
-
# true: Including 8-bit character
|
29
|
+
# @return [Boolean] false: ASCII Characters only, true: Including 8-bit character
|
35
30
|
def is_8bit(argvs)
|
36
31
|
v = argvs.to_s
|
37
|
-
return
|
38
|
-
return true
|
32
|
+
return false if v.empty?
|
33
|
+
return true unless v =~ /\A[\x00-\x7f]*\z/
|
39
34
|
return false
|
40
35
|
end
|
41
36
|
|
@@ -54,12 +49,12 @@ module Sisimai
|
|
54
49
|
# Check if each element of the 2nd argument is aligned in the 1st argument or not
|
55
50
|
# @param [String] argv1 String to be checked
|
56
51
|
# @param [Array] argv2 List including the ordered strings
|
57
|
-
# @return [
|
52
|
+
# @return [Boolean]
|
58
53
|
# @since v5.0.0
|
59
54
|
def aligned(argv1, argv2)
|
60
|
-
return
|
61
|
-
return
|
62
|
-
return
|
55
|
+
return false if argv1.to_s.empty?
|
56
|
+
return false unless argv2.is_a? Array
|
57
|
+
return false unless argv2.size > 1
|
63
58
|
|
64
59
|
align = -1
|
65
60
|
right = 0
|
@@ -80,7 +75,7 @@ module Sisimai
|
|
80
75
|
# @param [Boolean] loose Loose check flag
|
81
76
|
# @return [String] Plain text
|
82
77
|
def to_plain(argv1 = '', loose = false)
|
83
|
-
return
|
78
|
+
return "" if argv1.empty?
|
84
79
|
|
85
80
|
plain = argv1
|
86
81
|
if loose || plain =~ Match[:html] || plain =~ Match[:body]
|
@@ -89,20 +84,15 @@ module Sisimai
|
|
89
84
|
# 3. <a href = 'http://...'>...</a> to " http://... "
|
90
85
|
# 4. <a href = 'mailto:...'>...</a> to " Value <mailto:...> "
|
91
86
|
plain.scrub!('?')
|
92
|
-
plain.gsub
|
93
|
-
plain.gsub
|
94
|
-
plain.gsub
|
95
|
-
plain.gsub
|
96
|
-
|
87
|
+
plain = plain.gsub(%r|<head>.+</head>|im, '')
|
88
|
+
plain = plain.gsub(%r|<style.+?>.+</style>|im, '')
|
89
|
+
plain = plain.gsub(%r|<a\s+href\s*=\s*['"](https?://.+?)['"].*?>(.*?)</a>|i, '[\2](\1)')
|
90
|
+
plain = plain.gsub(%r|<a\s+href\s*=\s*["']mailto:([^\s]+?)["']>(.*?)</a>|i, '[\2](mailto:\1)')
|
97
91
|
plain = plain.gsub(/<[^<@>]+?>\s*/, ' ') # Delete HTML tags except <neko@example.jp>
|
98
92
|
plain = plain.gsub(/</, '<').gsub(/>/, '>') # Convert to angle brackets
|
99
93
|
plain = plain.gsub(/&/, '&').gsub(/ /, ' ') # Convert to "&"
|
100
94
|
plain = plain.gsub(/"/, '"').gsub(/'/, "'") # Convert to " and '
|
101
|
-
|
102
|
-
if argv1.size > plain.size
|
103
|
-
plain = plain.squeeze(' ')
|
104
|
-
plain << "\n"
|
105
|
-
end
|
95
|
+
plain = "#{plain.squeeze(' ')}\n" if argv1.size > plain.size
|
106
96
|
end
|
107
97
|
|
108
98
|
return plain
|
@@ -113,7 +103,7 @@ module Sisimai
|
|
113
103
|
# @param [String] argv2 Encoding name before converting
|
114
104
|
# @return [String] UTF-8 Encoded string
|
115
105
|
def to_utf8(argv1 = '', argv2 = nil)
|
116
|
-
return
|
106
|
+
return "" if argv1.empty?
|
117
107
|
|
118
108
|
encodefrom = argv2 || false
|
119
109
|
getencoded = ''
|
data/lib/sisimai/version.rb
CHANGED
data/lib/sisimai.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# Sisimai is a library that decodes complex and diverse bounce emails and
|
2
|
-
# the delivery failure, such as the reason for the bounce and the recipient
|
3
|
-
# structured data. It is also possible to output in JSON format.
|
1
|
+
# Sisimai (pronounced /ɕi.ɕi.ma.i/) is a library that decodes complex and diverse bounce emails and
|
2
|
+
# outputs the results of the delivery failure, such as the reason for the bounce and the recipient
|
3
|
+
# email address, in structured data. It is also possible to output in JSON format.
|
4
4
|
require 'sisimai/version'
|
5
5
|
module Sisimai
|
6
6
|
class << self
|
@@ -30,17 +30,17 @@ module Sisimai
|
|
30
30
|
while r = mail.data.read do
|
31
31
|
# Read and decode each email file
|
32
32
|
path = mail.data.path
|
33
|
-
args = {
|
33
|
+
args = {data: r, hook: c___[0], origin: path, delivered: argv1[:delivered], vacation: argv1[:vacation]}
|
34
34
|
fact = Sisimai::Fact.rise(**args) || []
|
35
35
|
|
36
36
|
if c___[1]
|
37
37
|
# Run the callback function specified with "c___" parameter of Sisimai.rise after reading
|
38
38
|
# each email file in Maildir/ every time
|
39
|
-
args = {
|
39
|
+
args = {'kind' => kind, 'mail' => r, 'path' => path, 'fact' => fact}
|
40
40
|
begin
|
41
41
|
c___[1].call(args) if c___[1].is_a?(Proc)
|
42
42
|
rescue StandardError => ce
|
43
|
-
warn ' ***warning: Something is wrong in the second element of the ":c___":'
|
43
|
+
warn ' ***warning: Something is wrong in the second element of the ":c___":' + ce.to_s
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -61,7 +61,7 @@ module Sisimai
|
|
61
61
|
# @options argv1 [Lambda] hook Lambda object to be called back
|
62
62
|
# @return [String] Decoded data as JSON text
|
63
63
|
def dump(argv0, **argv1)
|
64
|
-
return
|
64
|
+
return "" unless argv0
|
65
65
|
nyaan = Sisimai.rise(argv0, **argv1) || []
|
66
66
|
|
67
67
|
if RUBY_PLATFORM.start_with?('java')
|
@@ -81,14 +81,14 @@ module Sisimai
|
|
81
81
|
table = {}
|
82
82
|
|
83
83
|
%w[Lhost ARF RFC3464 RFC3834].each do |e|
|
84
|
-
r =
|
84
|
+
r = "Sisimai::#{e}"
|
85
85
|
require r.gsub('::', '/').downcase
|
86
86
|
|
87
87
|
if e == 'Lhost'
|
88
88
|
# Sisimai::Lhost::*
|
89
89
|
Module.const_get(r).send(:index).each do |ee|
|
90
90
|
# Load and get the value of "description" from each module
|
91
|
-
rr =
|
91
|
+
rr = "Sisimai::#{e}::#{ee}"
|
92
92
|
require rr.gsub('::', '/').downcase
|
93
93
|
table[rr.to_sym] = Module.const_get(rr).send(:description)
|
94
94
|
end
|
@@ -112,7 +112,7 @@ module Sisimai
|
|
112
112
|
names += %w[Delivered Feedback Undefined Vacation]
|
113
113
|
while e = names.shift do
|
114
114
|
# Call .description() method of Sisimai::Reason::*
|
115
|
-
r =
|
115
|
+
r = "Sisimai::Reason::#{e}"
|
116
116
|
require r.gsub('::', '/').downcase
|
117
117
|
table[e.to_sym] = Module.const_get(r).send(:description)
|
118
118
|
end
|
@@ -124,7 +124,7 @@ module Sisimai
|
|
124
124
|
# @param [String] Error message text
|
125
125
|
# @return [String] Reason text
|
126
126
|
def match(argvs = '')
|
127
|
-
return
|
127
|
+
return "" if argvs.empty?
|
128
128
|
require 'sisimai/reason'
|
129
129
|
return Sisimai::Reason.match(argvs.downcase)
|
130
130
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Return-Path: <>
|
2
|
+
Delivered-To: neko@e.nyaaaan.example.onmicrosoft.com
|
3
|
+
Received: from relay.nyaaaan.example.onmicrosoft.com (unknown [192.0.2.25])
|
4
|
+
by ee.nyaaaan.example.onmicrosoft.com (Postfix) with ESMTPS id JFzCqYfyTkzvHbZW
|
5
|
+
for <neko@e.nyaaaan.example.onmicrosoft.com>; Sun, 25 May 2025 22:22:22 +0900 (JST)
|
6
|
+
Received: from cat.example.co.jp (ip-192-0-2-225.us-west-1.compute.internal [192.0.2.225])
|
7
|
+
by relay.nyaaaan.example.onmicrosoft.com (Postfix) with ESMTPS id D09y2f7PVFzmfJf9
|
8
|
+
for <kijitora@nyaaaan.example.onmicrosoft.com>; Sun, 25 May 2025 22:22:22 +0900 (JST)
|
9
|
+
Message-ID: <20250525222222.00002.nekochan@cat.example.co.jp>
|
10
|
+
Date: 25 May 2025 22:22:22 +0900
|
11
|
+
From: MAILER-DAEMON@cat.example.co.jp
|
12
|
+
To: kijitora@nyaaaan.example.onmicrosoft.com
|
13
|
+
Subject: failure notice
|
14
|
+
|
15
|
+
Hi. This is the qmail-send program at cat.example.co.jp.
|
16
|
+
I'm afraid I wasn't able to deliver your message to the following addresses.
|
17
|
+
This is a permanent error; I've given up. Sorry it didn't work out.
|
18
|
+
|
19
|
+
<sabineko@example.onmicrosoft.com>:
|
20
|
+
198.51.100.25 failed after I sent the message.
|
21
|
+
Remote host said: 550 5.7.515 Access denied, sending domain NYAAAAN.EXAMPLE.JP doesn't meet the required authentication level. The sender's domain in the 5322.From address doesn't meet the authentication requirements defined for the sender. To learn how to fix this see: https://go.microsoft.com/fwlink/p/?linkid=2319303 Spf= Fail , Dkim= Pass , DMARC= Pass [0000000000000.JPNP255.PROD.OUTLOOK.COM 2025-05-25T22:22:22.000Z 0000000000000EEE] [0000000000000.jpnprd00.prod.outlook.com 2025-05-25T22:22:22.000Z 0000000000000EEE] [0000000000000EE.apcprd00.prod.outlook.com 2025-05-25T22:22:22.000Z 0000000000000EEE]
|
22
|
+
|
23
|
+
--- Below this line is a copy of the message.
|
24
|
+
|
25
|
+
Return-Path: <kijitora@nyaaaan.example.onmicrosoft.com>
|
26
|
+
Received: (qmail 32768 invoked by uid 99); 25 May 2025 22:22:22 +0900
|
27
|
+
Delivered-To: mikeneko@example.org
|
28
|
+
Received: (qmail 32767 invoked by uid 99); 25 May 2025 22:22:22 +0900
|
29
|
+
Received: from relay-2.nyaaaan.example.onmicrosoft.com (203.0.113.25)
|
30
|
+
by cat.example.co.jp with ESMTPS (AES256-GCM-SHA384 encrypted); 25 May 2025 22:22:22 +0900
|
31
|
+
Received: from relay-1.nyaaaan.example.onmicrosoft.com (unknown [192.0.2.254])
|
32
|
+
by relay-2.nyaaaan.example.onmicrosoft.com (Postfix) with ESMTP id djK8m53Kqpzf8pK9
|
33
|
+
for <mikeneko@example.org>; Sun, 25 May 2025 22:22:22 +0900 (JST)
|
34
|
+
DKIM-Filter: OpenDKIM Filter v2.11.0 relay-2.nyaaaan.example.onmicrosoft.com djK8m53Kqpzf8pK9
|
35
|
+
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nyaaaan.example.onmicrosoft.com;
|
36
|
+
Content-Type: text/plain; charset=us-ascii
|
37
|
+
MIME-Version: 1.0
|
38
|
+
From: <kijitora@nyaaaan.example.onmicrosoft.com>
|
39
|
+
Date: Sun, 25 May 2025 22:22:22 +0900
|
40
|
+
Subject: Nyaaaan?
|
41
|
+
Message-Id: <5E82FFFE9D95.88616A92ACCB@relay-1.nyaaaan.example.onmicrosoft.com>
|
42
|
+
To: <mikeneko@example.org>
|
43
|
+
|
44
|
+
Nyaaaan?
|
45
|
+
|