sisimai 5.4.1-java → 5.6.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/codecovio.yml +1 -1
- data/.github/workflows/rake-test.yml +1 -1
- data/ChangeLog.md +56 -0
- data/LICENSE +1 -1
- data/Makefile +2 -4
- data/README-JA.md +29 -23
- data/README.md +30 -24
- data/lib/sisimai/address.rb +92 -44
- data/lib/sisimai/arf.rb +7 -8
- data/lib/sisimai/datetime.rb +2 -2
- data/lib/sisimai/fact/json.rb +1 -2
- data/lib/sisimai/fact/yaml.rb +1 -2
- data/lib/sisimai/fact.rb +76 -36
- data/lib/sisimai/lda.rb +2 -2
- data/lib/sisimai/lhost/activehunter.rb +4 -5
- data/lib/sisimai/lhost/amazonses.rb +4 -5
- data/lib/sisimai/lhost/apachejames.rb +2 -2
- data/lib/sisimai/lhost/biglobe.rb +6 -6
- data/lib/sisimai/lhost/courier.rb +7 -7
- data/lib/sisimai/lhost/domino.rb +6 -6
- data/lib/sisimai/lhost/dragonfly.rb +5 -5
- data/lib/sisimai/lhost/einsundeins.rb +5 -5
- data/lib/sisimai/lhost/exchange2003.rb +7 -7
- data/lib/sisimai/lhost/exchange2007.rb +5 -5
- data/lib/sisimai/lhost/exim.rb +13 -15
- data/lib/sisimai/lhost/ezweb.rb +3 -2
- data/lib/sisimai/lhost/fml.rb +9 -9
- data/lib/sisimai/lhost/gmail.rb +9 -9
- data/lib/sisimai/lhost/gmx.rb +3 -3
- data/lib/sisimai/lhost/googlegroups.rb +6 -7
- data/lib/sisimai/lhost/googleworkspace.rb +5 -6
- data/lib/sisimai/lhost/imailserver.rb +4 -4
- data/lib/sisimai/lhost/kddi.rb +4 -4
- data/lib/sisimai/lhost/mailfoundry.rb +3 -3
- data/lib/sisimai/lhost/{mailmarshalsmtp.rb → mailmarshal.rb} +5 -5
- data/lib/sisimai/lhost/messagingserver.rb +8 -8
- data/lib/sisimai/lhost/mfilter.rb +8 -4
- data/lib/sisimai/lhost/mimecast.rb +105 -0
- data/lib/sisimai/lhost/notes.rb +5 -5
- data/lib/sisimai/lhost/opensmtpd.rb +5 -5
- data/lib/sisimai/lhost/postfix.rb +38 -32
- data/lib/sisimai/lhost/qmail.rb +11 -11
- data/lib/sisimai/lhost/sendmail.rb +13 -13
- data/lib/sisimai/lhost/{interscanmss.rb → trendmicro.rb} +8 -9
- data/lib/sisimai/lhost/v5sendmail.rb +7 -7
- data/lib/sisimai/lhost/verizon.rb +3 -3
- data/lib/sisimai/lhost/x1.rb +7 -4
- data/lib/sisimai/lhost/x2.rb +40 -12
- data/lib/sisimai/lhost/x3.rb +3 -3
- data/lib/sisimai/lhost/x6.rb +2 -2
- data/lib/sisimai/lhost/zoho.rb +5 -5
- data/lib/sisimai/lhost.rb +18 -17
- data/lib/sisimai/mail/maildir.rb +4 -4
- data/lib/sisimai/mail/mbox.rb +4 -4
- data/lib/sisimai/mail/memory.rb +1 -1
- data/lib/sisimai/mail/stdin.rb +2 -2
- data/lib/sisimai/message.rb +36 -34
- data/lib/sisimai/order.rb +5 -4
- data/lib/sisimai/reason/authfailure.rb +10 -14
- data/lib/sisimai/reason/badreputation.rb +8 -8
- data/lib/sisimai/reason/blocked.rb +58 -83
- data/lib/sisimai/reason/contenterror.rb +15 -10
- data/lib/sisimai/reason/{mesgtoobig.rb → emailtoolarge.rb} +23 -26
- data/lib/sisimai/reason/expired.rb +17 -24
- data/lib/sisimai/reason/failedstarttls.rb +1 -1
- data/lib/sisimai/reason/filtered.rb +14 -18
- data/lib/sisimai/reason/hasmoved.rb +3 -2
- data/lib/sisimai/reason/hostunknown.rb +18 -21
- data/lib/sisimai/reason/mailboxfull.rb +28 -50
- data/lib/sisimai/reason/mailererror.rb +1 -1
- data/lib/sisimai/reason/networkerror.rb +17 -17
- data/lib/sisimai/reason/norelaying.rb +20 -20
- data/lib/sisimai/reason/notaccept.rb +7 -10
- data/lib/sisimai/reason/notcompliantrfc.rb +6 -10
- data/lib/sisimai/reason/policyviolation.rb +12 -28
- data/lib/sisimai/reason/ratelimited.rb +62 -0
- data/lib/sisimai/reason/rejected.rb +46 -58
- data/lib/sisimai/reason/requireptr.rb +14 -26
- data/lib/sisimai/reason/securityerror.rb +14 -20
- data/lib/sisimai/reason/spamdetected.rb +52 -102
- data/lib/sisimai/reason/suspend.rb +27 -24
- data/lib/sisimai/reason/systemerror.rb +20 -24
- data/lib/sisimai/reason/systemfull.rb +2 -2
- data/lib/sisimai/reason/userunknown.rb +82 -114
- data/lib/sisimai/reason/vacation.rb +1 -1
- data/lib/sisimai/reason/virusdetected.rb +7 -9
- data/lib/sisimai/reason.rb +26 -26
- data/lib/sisimai/rfc1123.rb +58 -18
- data/lib/sisimai/rfc1894.rb +6 -8
- data/lib/sisimai/rfc2045.rb +25 -13
- data/lib/sisimai/rfc3464/thirdparty.rb +2 -3
- data/lib/sisimai/rfc3464.rb +6 -6
- data/lib/sisimai/rfc3834.rb +18 -8
- data/lib/sisimai/rfc5322.rb +9 -9
- data/lib/sisimai/rfc791.rb +2 -2
- data/lib/sisimai/rhost/aol.rb +4 -1
- data/lib/sisimai/rhost/apple.rb +15 -11
- data/lib/sisimai/rhost/cloudflare.rb +2 -0
- data/lib/sisimai/rhost/cox.rb +31 -25
- data/lib/sisimai/rhost/facebook.rb +24 -18
- data/lib/sisimai/rhost/franceptt.rb +92 -38
- data/lib/sisimai/rhost/godaddy.rb +34 -7
- data/lib/sisimai/rhost/google.rb +69 -70
- data/lib/sisimai/rhost/gsuite.rb +1 -1
- data/lib/sisimai/rhost/iua.rb +1 -1
- data/lib/sisimai/rhost/kddi.rb +1 -1
- data/lib/sisimai/rhost/messagelabs.rb +160 -2
- data/lib/sisimai/rhost/microsoft.rb +154 -107
- data/lib/sisimai/rhost/mimecast.rb +64 -55
- data/lib/sisimai/rhost/nttdocomo.rb +70 -90
- data/lib/sisimai/rhost/outlook.rb +1 -1
- data/lib/sisimai/rhost/spectrum.rb +8 -8
- data/lib/sisimai/rhost/tencent.rb +12 -13
- data/lib/sisimai/rhost/yahooinc.rb +9 -10
- data/lib/sisimai/rhost/zoho.rb +72 -0
- data/lib/sisimai/rhost.rb +4 -3
- data/lib/sisimai/smtp/command.rb +4 -2
- data/lib/sisimai/smtp/reply.rb +11 -4
- data/lib/sisimai/smtp/status.rb +67 -98
- data/lib/sisimai/smtp/transcript.rb +3 -3
- data/lib/sisimai/string.rb +4 -23
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +1 -1
- data/set-of-emails/maildir/bsd/lhost-exim-56.eml +40 -40
- data/set-of-emails/maildir/bsd/lhost-mfilter-05.eml +41 -0
- data/set-of-emails/maildir/bsd/lhost-mimecast-01.eml +46 -0
- data/set-of-emails/maildir/bsd/lhost-mimecast-02.eml +59 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-79.eml +81 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-80.eml +84 -0
- data/set-of-emails/maildir/bsd/lhost-x1-03.eml +26 -0
- data/set-of-emails/maildir/bsd/lhost-x1-04.eml +45 -0
- data/set-of-emails/maildir/bsd/lhost-x2-07.eml +30 -0
- data/set-of-emails/maildir/bsd/rfc3464-66.eml +170 -0
- data/set-of-emails/maildir/bsd/rfc3834-06.eml +59 -0
- data/set-of-emails/maildir/bsd/rhost-zoho-01.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-zoho-02.eml +86 -0
- data/set-of-emails/maildir/bsd/rhost-zoho-03.eml +87 -0
- data/set-of-emails/maildir/bsd/rhost-zoho-04.eml +86 -0
- data/set-of-emails/maildir/not/rb-issue-368-bug.eml +39 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +28 -13
- data/lib/sisimai/reason/exceedlimit.rb +0 -47
- data/lib/sisimai/reason/speeding.rb +0 -47
- data/lib/sisimai/reason/toomanyconn.rb +0 -59
- /data/set-of-emails/maildir/bsd/{lhost-mailmarshalsmtp-02.eml → lhost-mailmarshal-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-interscanmss-01.eml → lhost-trendmicro-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-interscanmss-02.eml → lhost-trendmicro-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-interscanmss-03.eml → lhost-trendmicro-03.eml} +0 -0
|
@@ -14,13 +14,10 @@ module Sisimai
|
|
|
14
14
|
#
|
|
15
15
|
module VirusDetected
|
|
16
16
|
class << self
|
|
17
|
-
Index = [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'virus detected',
|
|
22
|
-
'virus phishing/malicious_url detected',
|
|
23
|
-
'your message was infected with a virus',
|
|
17
|
+
Index = ["it has a potentially executable attachment"].freeze
|
|
18
|
+
Pairs = [
|
|
19
|
+
["message was ", "ected", " virus"],
|
|
20
|
+
["virus", " detected"],
|
|
24
21
|
].freeze
|
|
25
22
|
|
|
26
23
|
def text; return 'virusdetected'; end
|
|
@@ -31,8 +28,9 @@ module Sisimai
|
|
|
31
28
|
# @return [Boolean] false: Did not match, true: Matched
|
|
32
29
|
# @since 4.22.0
|
|
33
30
|
def match(argv1)
|
|
34
|
-
return false
|
|
31
|
+
return false if argv1.nil? || argv1.empty?
|
|
35
32
|
return true if Index.any? { |a| argv1.include?(a) }
|
|
33
|
+
return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) }
|
|
36
34
|
return false
|
|
37
35
|
end
|
|
38
36
|
|
|
@@ -47,7 +45,7 @@ module Sisimai
|
|
|
47
45
|
# to be sent before the SMTP DATA command because all the MTAs read the headers and the
|
|
48
46
|
# entire message body after the DATA command.
|
|
49
47
|
return true if argvs['reason'] == 'virusdetected'
|
|
50
|
-
return false if
|
|
48
|
+
return false if Sisimai::SMTP::Command::ExceptDATA.include?(argvs['command'])
|
|
51
49
|
return match(argvs['diagnosticcode'].downcase)
|
|
52
50
|
end
|
|
53
51
|
|
data/lib/sisimai/reason.rb
CHANGED
|
@@ -7,10 +7,10 @@ module Sisimai
|
|
|
7
7
|
# @return [Array] Reason list
|
|
8
8
|
def index
|
|
9
9
|
return %w[
|
|
10
|
-
AuthFailure BadReputation Blocked ContentError
|
|
11
|
-
HasMoved HostUnknown MailboxFull MailerError
|
|
12
|
-
OnHold Rejected NoRelaying
|
|
13
|
-
|
|
10
|
+
AuthFailure BadReputation Blocked ContentError EmailTooLarge Expired FailedSTARTTLS Filtered
|
|
11
|
+
HasMoved HostUnknown MailboxFull MailerError NetworkError NotAccept NotCompliantRFC RateLimited
|
|
12
|
+
OnHold Rejected NoRelaying SpamDetected VirusDetected PolicyViolation SecurityError Suspend
|
|
13
|
+
RequirePTR SystemError SystemFull Suppressed UserUnknown SyntaxError
|
|
14
14
|
]
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -46,20 +46,20 @@ module Sisimai
|
|
|
46
46
|
GetRetried = Sisimai::Reason.retry
|
|
47
47
|
ClassOrder = [
|
|
48
48
|
%w[
|
|
49
|
-
MailboxFull
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
MailboxFull EmailTooLarge Suspend HasMoved NoRelaying AuthFailure UserUnknown Filtered
|
|
50
|
+
RequirePTR NotCompliantRFC BadReputation ContentError Rejected HostUnknown SpamDetected
|
|
51
|
+
RateLimited Blocked
|
|
52
52
|
],
|
|
53
53
|
%w[
|
|
54
|
-
MailboxFull AuthFailure BadReputation
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
MailboxFull AuthFailure BadReputation SpamDetected VirusDetected PolicyViolation NoRelaying
|
|
55
|
+
SystemError NetworkError Suspend ContentError SystemFull NotAccept Expired FailedSTARTTLS
|
|
56
|
+
SecurityError Suppressed MailerError
|
|
57
57
|
],
|
|
58
58
|
%w[
|
|
59
|
-
MailboxFull
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
MailboxFull EmailTooLarge Suspend UserUnknown Filtered Rejected HostUnknown SpamDetected
|
|
60
|
+
RateLimited Blocked SpamDetected AuthFailure FailedSTARTTLS SecurityError SystemError
|
|
61
|
+
NetworkError Suspend Expired ContentError HasMoved SystemFull NotAccept MailerError
|
|
62
|
+
NoRelaying Suppressed SyntaxError OnHold
|
|
63
63
|
]
|
|
64
64
|
]
|
|
65
65
|
|
|
@@ -68,11 +68,11 @@ module Sisimai
|
|
|
68
68
|
# @return [String] Bounce reason or an empty string if the argument is missing or not Hash
|
|
69
69
|
# @see anotherone
|
|
70
70
|
def find(argvs)
|
|
71
|
-
return ""
|
|
72
|
-
|
|
71
|
+
return "" if argvs.nil?
|
|
72
|
+
if GetRetried[argvs['reason']].nil?
|
|
73
73
|
# Return reason text already decided except reason match with the regular expression of
|
|
74
74
|
# retry() method.
|
|
75
|
-
return argvs['reason']
|
|
75
|
+
return argvs['reason'] if argvs['reason'].empty? == false
|
|
76
76
|
end
|
|
77
77
|
return 'delivered' if argvs['deliverystatus'].start_with?('2.')
|
|
78
78
|
|
|
@@ -94,7 +94,7 @@ module Sisimai
|
|
|
94
94
|
warn " ***warning: Failed to load #{p}"
|
|
95
95
|
next
|
|
96
96
|
end
|
|
97
|
-
next
|
|
97
|
+
next if r.true(argvs) == false
|
|
98
98
|
reasontext = r.text
|
|
99
99
|
break
|
|
100
100
|
end
|
|
@@ -113,7 +113,7 @@ module Sisimai
|
|
|
113
113
|
# Try to match with message patterns in Sisimai::Reason::Vacation
|
|
114
114
|
require 'sisimai/reason/vacation'
|
|
115
115
|
reasontext = 'vacation' if Sisimai::Reason::Vacation.match(issuedcode.downcase)
|
|
116
|
-
reasontext ||= 'onhold'
|
|
116
|
+
reasontext ||= 'onhold' if issuedcode.empty? == false
|
|
117
117
|
reasontext ||= 'undefined'
|
|
118
118
|
end
|
|
119
119
|
end
|
|
@@ -125,7 +125,7 @@ module Sisimai
|
|
|
125
125
|
# @return [String, Nil] Bounce reason or nli if the argument is missing or not Hash
|
|
126
126
|
# @see find()
|
|
127
127
|
def anotherone(argvs)
|
|
128
|
-
return argvs['reason']
|
|
128
|
+
return argvs['reason'] if argvs['reason'].empty? == false
|
|
129
129
|
|
|
130
130
|
require 'sisimai/smtp/status'
|
|
131
131
|
issuedcode = argvs['diagnosticcode'].downcase || ''
|
|
@@ -150,11 +150,11 @@ module Sisimai
|
|
|
150
150
|
next
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
next
|
|
153
|
+
next if r.match(issuedcode) == false
|
|
154
154
|
reasontext = e.downcase
|
|
155
155
|
break
|
|
156
156
|
end
|
|
157
|
-
break
|
|
157
|
+
break if reasontext.empty? == false
|
|
158
158
|
|
|
159
159
|
# Check the value of Status:
|
|
160
160
|
code2digit = statuscode[0, 3] || ''
|
|
@@ -175,7 +175,7 @@ module Sisimai
|
|
|
175
175
|
require 'sisimai/reason/syntaxerror'
|
|
176
176
|
reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
|
|
177
177
|
end
|
|
178
|
-
break
|
|
178
|
+
break if reasontext.empty? == false
|
|
179
179
|
|
|
180
180
|
# Check the value of Action: field, first
|
|
181
181
|
if actiontext.start_with?('delayed', 'expired')
|
|
@@ -195,7 +195,7 @@ module Sisimai
|
|
|
195
195
|
# @param [String] argv1 Error message
|
|
196
196
|
# @return [String] Bounce reason
|
|
197
197
|
def match(argv1)
|
|
198
|
-
return ""
|
|
198
|
+
return "" if argv1.nil?
|
|
199
199
|
|
|
200
200
|
reasontext = ''
|
|
201
201
|
issuedcode = argv1.downcase
|
|
@@ -214,11 +214,11 @@ module Sisimai
|
|
|
214
214
|
next
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
next
|
|
217
|
+
next if r.match(issuedcode) == false
|
|
218
218
|
reasontext = r.text
|
|
219
219
|
break
|
|
220
220
|
end
|
|
221
|
-
return reasontext
|
|
221
|
+
return reasontext if reasontext.empty? == false
|
|
222
222
|
|
|
223
223
|
if issuedcode.upcase.include?('X-UNIX; ')
|
|
224
224
|
# X-Unix; ...
|
data/lib/sisimai/rfc1123.rb
CHANGED
|
@@ -3,6 +3,7 @@ module Sisimai
|
|
|
3
3
|
module RFC1123
|
|
4
4
|
class << self
|
|
5
5
|
require 'sisimai/string'
|
|
6
|
+
require 'sisimai/rfc791'
|
|
6
7
|
Sandwiched = [
|
|
7
8
|
# (Postfix) postfix/src/smtp/smtp_proto.c: "host %s said: %s (in reply to %s)",
|
|
8
9
|
# - <kijitora@example.com>: host re2.example.com[198.51.100.2] said: 550 ...
|
|
@@ -37,32 +38,72 @@ module Sisimai
|
|
|
37
38
|
# @return [Boolean] false: is not a valid internet host, true: is a valid interneet host
|
|
38
39
|
# @since v5.2.0
|
|
39
40
|
def is_internethost(argv0 = '')
|
|
40
|
-
return false
|
|
41
|
-
return
|
|
41
|
+
return false if argv0.nil? || argv0.size < 4 || argv0.size > 255
|
|
42
|
+
return true if argv0 == 'localhost' || argv0 == 'localhost6'
|
|
42
43
|
return false if argv0.include?(".") == false
|
|
43
44
|
return false if argv0.include?("..") || argv0.include?("--")
|
|
44
45
|
return false if argv0.start_with?(".", "-") || argv0.end_with?("-")
|
|
45
46
|
|
|
46
|
-
hostnameok = true
|
|
47
47
|
characters = argv0.upcase.split("")
|
|
48
48
|
characters.each do |e|
|
|
49
49
|
# Check each characater is a number or an alphabet
|
|
50
50
|
f = e.ord
|
|
51
|
-
if f < 45
|
|
52
|
-
if f == 47
|
|
53
|
-
if f > 57 && f < 65
|
|
54
|
-
if f > 90
|
|
51
|
+
return false if f < 45 # 45 = '-'
|
|
52
|
+
return false if f == 47 # 47 = '/'
|
|
53
|
+
return false if f > 57 && f < 65 # 57 = '9', 65 = 'A'
|
|
54
|
+
return false if f > 90 # 90 = 'Z'
|
|
55
55
|
end
|
|
56
|
-
return false if hostnameok == false
|
|
57
56
|
|
|
58
57
|
p1 = argv0.rindex(".")
|
|
59
58
|
cv = argv0[p1 + 1, argv0.size - p1]; return false if cv.size > 63
|
|
60
59
|
cv.split("").each do |e|
|
|
61
60
|
# The top level domain should not include a number
|
|
62
|
-
f = e.ord
|
|
63
|
-
if f > 47 && f < 58 then hostnameok = false; break; end
|
|
61
|
+
f = e.ord; return false if f > 47 && f < 58
|
|
64
62
|
end
|
|
65
|
-
return
|
|
63
|
+
return true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# returns true if the domain part is [IPv4:...] or [IPv6:...].
|
|
67
|
+
# @param [String] email Email address.
|
|
68
|
+
# @return [Boolean] false: the domain part is not a domain literal.
|
|
69
|
+
# true: the domain part is a domain literal.
|
|
70
|
+
def is_domainliteral(email)
|
|
71
|
+
return false if email.is_a?(::String) == false
|
|
72
|
+
|
|
73
|
+
email = email.delete_prefix('<').delete_suffix('>')
|
|
74
|
+
return false if email.size < 16 # e@[IPv4:0.0.0.0] is 16 characters
|
|
75
|
+
return false if email[-1, 1] != ']'
|
|
76
|
+
|
|
77
|
+
lastb = email.rindex('@[IPv'); return false if lastb == false
|
|
78
|
+
dpart = email.split('@')[-1]
|
|
79
|
+
|
|
80
|
+
if email.include?('@[IPv4:')
|
|
81
|
+
# neko@[IPv4:192.0.2.25]
|
|
82
|
+
ipv4a = email[lastb + 7, 16].delete_suffix(']')
|
|
83
|
+
return Sisimai::RFC791.is_ipv4address(ipv4a)
|
|
84
|
+
|
|
85
|
+
elsif email.include?('@[IPv6:')
|
|
86
|
+
# neko@[IPv6:2001:0DB8:0000:0000:0000:0000:0000:0001]
|
|
87
|
+
# neko@[IPv6:2001:0DB8:0000:0000:0000:0000:0000:0001]
|
|
88
|
+
# IPv6-address-literal = "IPv6:" IPv6-addr
|
|
89
|
+
# IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
|
|
90
|
+
# IPv6-hex = 1*4HEXDIG
|
|
91
|
+
# IPv6-full = IPv6-hex 7(":" IPv6-hex)
|
|
92
|
+
# IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
|
|
93
|
+
# [IPv6-hex *5(":" IPv6-hex)]
|
|
94
|
+
# ; The "::" represents at least 2 16-bit groups of
|
|
95
|
+
# ; zeros. No more than 6 groups in addition to the
|
|
96
|
+
# ; "::" may be present.
|
|
97
|
+
# IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
|
|
98
|
+
# IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
|
|
99
|
+
# [IPv6-hex *3(":" IPv6-hex) ":"]
|
|
100
|
+
# IPv4-address-literal
|
|
101
|
+
# ; The "::" represents at least 2 16-bit groups of
|
|
102
|
+
# ; zeros. No more than 4 groups in addition to the
|
|
103
|
+
# ; "::" and IPv4-address-literal may be present.
|
|
104
|
+
return true if dpart.size > 2 && dpart.rindex(':') > 7
|
|
105
|
+
end
|
|
106
|
+
return false
|
|
66
107
|
end
|
|
67
108
|
|
|
68
109
|
# find() returns a valid internet hostname found from the argument
|
|
@@ -70,8 +111,7 @@ module Sisimai
|
|
|
70
111
|
# @return string A valid internet hostname found in the argument
|
|
71
112
|
# @since v5.2.0
|
|
72
113
|
def find(argv1 = "")
|
|
73
|
-
return ""
|
|
74
|
-
return "" unless argv1.size > 4
|
|
114
|
+
return "" if argv1.nil? || argv1.size < 5
|
|
75
115
|
|
|
76
116
|
sourcetext = argv1.downcase
|
|
77
117
|
sourcelist = []
|
|
@@ -91,7 +131,7 @@ module Sisimai
|
|
|
91
131
|
Sandwiched.each do |e|
|
|
92
132
|
# Check a hostname exists between the $e->[0] and $e->[1] at array "Sandwiched"
|
|
93
133
|
# Each array in Sandwiched have 2 elements
|
|
94
|
-
next
|
|
134
|
+
next if Sisimai::String.aligned(sourcetext, e) == false
|
|
95
135
|
|
|
96
136
|
p1 = sourcetext.index(e[0])
|
|
97
137
|
p2 = sourcetext.index(e[1])
|
|
@@ -112,7 +152,7 @@ module Sisimai
|
|
|
112
152
|
end
|
|
113
153
|
ExistUntil.each do |e|
|
|
114
154
|
# ExistUntil have some strings, not an array
|
|
115
|
-
p1 = sourcetext.index(e); next
|
|
155
|
+
p1 = sourcetext.index(e); next if p1.nil?
|
|
116
156
|
sourcelist = sourcetext[0, p1].split(" ")
|
|
117
157
|
throw :MAKELIST
|
|
118
158
|
end
|
|
@@ -126,9 +166,9 @@ module Sisimai
|
|
|
126
166
|
e.chop! if e[-1, 1] == "." # Remove "." at the end of the string
|
|
127
167
|
e.delete!('[]()<>:;')
|
|
128
168
|
|
|
129
|
-
next
|
|
130
|
-
next
|
|
131
|
-
next
|
|
169
|
+
next if e.size < 4
|
|
170
|
+
next if e.include?(".") == false
|
|
171
|
+
next if Sisimai::RFC1123.is_internethost(e) == false
|
|
132
172
|
foundtoken << e
|
|
133
173
|
end
|
|
134
174
|
return "" if foundtoken.size == 0
|
data/lib/sisimai/rfc1894.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Sisimai
|
|
|
59
59
|
|
|
60
60
|
SubtypeSet = {"addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS"}.freeze
|
|
61
61
|
ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
|
|
62
|
-
Correction = {'deliverable' => 'delivered', 'expired' => '
|
|
62
|
+
Correction = {'deliverable' => 'delivered', 'expired' => 'failed', 'failure' => 'failed'}
|
|
63
63
|
FieldGroup = {
|
|
64
64
|
'original-recipient' => 'addr',
|
|
65
65
|
'final-recipient' => 'addr',
|
|
@@ -114,16 +114,16 @@ module Sisimai
|
|
|
114
114
|
|
|
115
115
|
FieldNames[0].each_key do |e|
|
|
116
116
|
# Per-Message fields
|
|
117
|
-
next
|
|
118
|
-
next
|
|
117
|
+
next if label != e
|
|
118
|
+
next if argv0.include?(FieldNames[0][label]) == false
|
|
119
119
|
match = 1; break
|
|
120
120
|
end
|
|
121
121
|
return match if match > 0
|
|
122
122
|
|
|
123
123
|
FieldNames[1].each_key do |e|
|
|
124
124
|
# Per-Recipient field
|
|
125
|
-
next
|
|
126
|
-
next
|
|
125
|
+
next if label != e
|
|
126
|
+
next if argv0.include?(FieldNames[1][label]) == false
|
|
127
127
|
match = 2; break
|
|
128
128
|
end
|
|
129
129
|
return match
|
|
@@ -147,9 +147,7 @@ module Sisimai
|
|
|
147
147
|
label = Sisimai::RFC1894.label(argv0)
|
|
148
148
|
group = FieldGroup[label] || ''
|
|
149
149
|
parts = argv0.split(":", 2); parts[1] = parts[1].nil? ? "" : Sisimai::String.sweep(parts[1])
|
|
150
|
-
|
|
151
|
-
return nil if group.empty?
|
|
152
|
-
return nil unless CapturesOn[group]
|
|
150
|
+
return nil if group.empty? || CapturesOn[group].nil?
|
|
153
151
|
|
|
154
152
|
# Try to match with each pattern of Per-Message field, Per-Recipient field
|
|
155
153
|
# - 0: Field-Name
|
data/lib/sisimai/rfc2045.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Sisimai
|
|
|
9
9
|
# @param [String] argvs String to be checked
|
|
10
10
|
# @return [Boolean] false: Not MIME encoded string, true: MIME encoded string
|
|
11
11
|
def is_encoded(argv1)
|
|
12
|
-
return false
|
|
12
|
+
return false if argv1.nil? || argv1.empty?
|
|
13
13
|
|
|
14
14
|
text1 = argv1.delete('"')
|
|
15
15
|
mime1 = false
|
|
@@ -69,7 +69,7 @@ module Sisimai
|
|
|
69
69
|
# utf8 => UTF-8
|
|
70
70
|
ctxcharset = 'UTF-8' if ctxcharset.casecmp('UTF8') == 0
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
if ctxcharset.casecmp('UTF-8') != 0
|
|
73
73
|
# Characterset is not UTF-8
|
|
74
74
|
begin
|
|
75
75
|
p = p.encode!('UTF-8', ctxcharset)
|
|
@@ -109,12 +109,11 @@ module Sisimai
|
|
|
109
109
|
def parameter(argv0 = '', argv1 = '')
|
|
110
110
|
return "" if argv0.empty?
|
|
111
111
|
parameterq = argv1.size > 0 ? argv1 + '=' : ''
|
|
112
|
-
paramindex = argv1.size > 0 ? argv0.index(parameterq) : 0
|
|
113
|
-
return '' unless paramindex
|
|
112
|
+
paramindex = argv1.size > 0 ? argv0.index(parameterq) : 0; return '' if paramindex.nil?
|
|
114
113
|
|
|
115
114
|
# Find the value of the parameter name specified in argv1
|
|
116
115
|
foundtoken = argv0[paramindex + parameterq.size, argv0.size].split(';', 2)[0] || ''
|
|
117
|
-
foundtoken = foundtoken.downcase
|
|
116
|
+
foundtoken = foundtoken.downcase if argv1 != 'boundary'
|
|
118
117
|
foundtoken = foundtoken.delete('"').delete("'")
|
|
119
118
|
return foundtoken
|
|
120
119
|
end
|
|
@@ -147,8 +146,8 @@ module Sisimai
|
|
|
147
146
|
return nil if block.empty?
|
|
148
147
|
|
|
149
148
|
(upperchunk, lowerchunk) = block.split("\n\n", 2)
|
|
150
|
-
return ['', ''] if
|
|
151
|
-
return ['', '']
|
|
149
|
+
return ['', ''] if upperchunk.nil? || upperchunk.empty?
|
|
150
|
+
return ['', ''] if upperchunk.index('Content-Type').nil?
|
|
152
151
|
|
|
153
152
|
headerpart = ['', ''] # ["text/plain; charset=iso-2022-jp; ...", "quoted-printable"]
|
|
154
153
|
multipart1 = [] # [headerpart, "body"]
|
|
@@ -224,8 +223,10 @@ module Sisimai
|
|
|
224
223
|
multiparts = argv1.split(Regexp.new(Regexp.escape(boundary01) + "\n"))
|
|
225
224
|
partstable = []
|
|
226
225
|
|
|
227
|
-
|
|
228
|
-
multiparts.
|
|
226
|
+
# Remove empty or useless preamble and epilogue of multipart/* block
|
|
227
|
+
multiparts.shift if multiparts[0].size < 8
|
|
228
|
+
return [] if multiparts.empty?
|
|
229
|
+
multiparts.pop if multiparts.size > 2 && multiparts[-1].size < 8
|
|
229
230
|
|
|
230
231
|
while e = multiparts.shift do
|
|
231
232
|
# Check each part and breaks up internal multipart/* block
|
|
@@ -234,8 +235,7 @@ module Sisimai
|
|
|
234
235
|
# There is nested multipart/* block
|
|
235
236
|
boundary02 = boundary(f[0], -1); next if boundary02.empty?
|
|
236
237
|
bodyinside = f[-1].split("\n\n", 2)[-1]
|
|
237
|
-
next
|
|
238
|
-
next unless bodyinside.index(boundary02)
|
|
238
|
+
next if bodyinside.size < 9 || bodyinside.index(boundary02).nil?
|
|
239
239
|
|
|
240
240
|
v = levelout(f[0], bodyinside)
|
|
241
241
|
partstable += v if v.size > 0
|
|
@@ -299,6 +299,16 @@ module Sisimai
|
|
|
299
299
|
if ctencoding == 'base64'
|
|
300
300
|
# Content-Transfer-Encoding: base64
|
|
301
301
|
bodystring = decodeB(bodyinside) || ''
|
|
302
|
+
dontappend = false; while first10 = bodystring[0,10] do
|
|
303
|
+
# Don't pick the decoded part as an error message when the part is
|
|
304
|
+
# - BASE64 encoded.
|
|
305
|
+
# - the value of the charset is not utf-8.
|
|
306
|
+
# - NOT a plain text.
|
|
307
|
+
break if Sisimai::String.aligned(e[0], ['charset', '=', 'utf-8'])
|
|
308
|
+
break unless first10 =~ /[\x00-\x08\x0E-\x1F\x7F-]/
|
|
309
|
+
dontappend = true; break
|
|
310
|
+
end
|
|
311
|
+
next if dontappend
|
|
302
312
|
|
|
303
313
|
elsif ctencoding == 'quoted-printable'
|
|
304
314
|
# Content-Transfer-Encoding: quoted-printable
|
|
@@ -328,7 +338,7 @@ module Sisimai
|
|
|
328
338
|
# the following errors:
|
|
329
339
|
# - incompatible character encodings: ASCII-8BIT and UTF-8
|
|
330
340
|
# - invalid byte sequence in UTF-8
|
|
331
|
-
|
|
341
|
+
if bodystring.encoding.to_s != 'UTF-8'
|
|
332
342
|
# ASCII-8BIT or other 8bit encodings
|
|
333
343
|
ctxcharset = parameter(e[0], 'charset')
|
|
334
344
|
if ctxcharset.empty?
|
|
@@ -346,6 +356,8 @@ module Sisimai
|
|
|
346
356
|
|
|
347
357
|
else
|
|
348
358
|
# There is no Content-Transfer-Encoding header in the part
|
|
359
|
+
be = bodyinside.encoding.to_s
|
|
360
|
+
bodyinside = Sisimai::String.to_utf8(bodyinside, be) if be != 'UTF-8'
|
|
349
361
|
bodystring += bodyinside
|
|
350
362
|
end
|
|
351
363
|
|
|
@@ -357,7 +369,7 @@ module Sisimai
|
|
|
357
369
|
end
|
|
358
370
|
|
|
359
371
|
# Append "\n" when the last character of $bodystring is not LF
|
|
360
|
-
bodystring += "\n\n"
|
|
372
|
+
bodystring += "\n\n" if bodystring[-2, 2] != "\n\n"
|
|
361
373
|
flattenout += bodystring
|
|
362
374
|
end
|
|
363
375
|
|
|
@@ -22,8 +22,7 @@ module Sisimai
|
|
|
22
22
|
# @param string argv1 A line of a bounce mail
|
|
23
23
|
# @return string An MTA name of the 3rd party
|
|
24
24
|
def returnedby(argv1 = "")
|
|
25
|
-
return ""
|
|
26
|
-
return "" unless argv1.start_with?("X-")
|
|
25
|
+
return "" if argv1.nil? || argv1.start_with?("X-") == false
|
|
27
26
|
|
|
28
27
|
ThirdParty.each_key do |e|
|
|
29
28
|
# Does the argument include the 3rd party specific field?
|
|
@@ -53,7 +52,7 @@ module Sisimai
|
|
|
53
52
|
MessagesOf = {
|
|
54
53
|
"bad-domain" => "hostunknown",
|
|
55
54
|
"bad-mailbox" => "userunknown",
|
|
56
|
-
"inactive-mailbox" => "
|
|
55
|
+
"inactive-mailbox" => "suspend",
|
|
57
56
|
"message-expired" => "expired",
|
|
58
57
|
"no-answer-from-host" => "networkerror",
|
|
59
58
|
"policy-related" => "policyviolation",
|
data/lib/sisimai/rfc3464.rb
CHANGED
|
@@ -151,7 +151,7 @@ module Sisimai
|
|
|
151
151
|
if o[0] == "final-recipient"
|
|
152
152
|
# Final-Recipient: rfc822; kijitora@example.jp
|
|
153
153
|
# Final-Recipient: x400; /PN=...
|
|
154
|
-
cv = Sisimai::Address.s3s4(o[2]); next
|
|
154
|
+
cv = Sisimai::Address.s3s4(o[2]); next if Sisimai::Address.is_emailaddress(cv) == false
|
|
155
155
|
cw = dscontents.size; next if cw > 0 && cv == dscontents[cw - 1]["recipient"]
|
|
156
156
|
|
|
157
157
|
if v["recipient"] != ""
|
|
@@ -177,18 +177,18 @@ module Sisimai
|
|
|
177
177
|
# Status: 4.0.0 (cat.example.net: host name lookup failure)
|
|
178
178
|
v["diagnosis"] += " #{o[4]} "
|
|
179
179
|
end
|
|
180
|
-
next
|
|
180
|
+
next if FieldTable[o[0]].nil?
|
|
181
181
|
next if o[3] == "host" && Sisimai::RFC1123.is_internethost(o[2]) == false
|
|
182
182
|
v[FieldTable[o[0]]] = o[2]
|
|
183
183
|
|
|
184
|
-
next
|
|
184
|
+
next if f != 1
|
|
185
185
|
permessage[FieldTable[o[0]]] = o[2]
|
|
186
186
|
end
|
|
187
187
|
else
|
|
188
188
|
# Check that the line is a continued line of the value of Diagnostic-Code: field or not
|
|
189
189
|
if e.start_with?("X-") && e.include?(": ")
|
|
190
190
|
# This line is a MTA-Specific fields begins with "X-"
|
|
191
|
-
next
|
|
191
|
+
next if Sisimai::RFC3464::ThirdParty.is3rdparty(e) == false
|
|
192
192
|
cv = Sisimai::RFC3464::ThirdParty.xfield(e)
|
|
193
193
|
|
|
194
194
|
if cv.size > 0 && FieldTable[cv[0].downcase] == nil
|
|
@@ -197,7 +197,7 @@ module Sisimai
|
|
|
197
197
|
v["reason"] = cv[4][p1 + 1, cv[4].size] if cv[4].start_with?("reason:")
|
|
198
198
|
else
|
|
199
199
|
# Set the value picked from "X-*" field to $dscontents when the current value is empty
|
|
200
|
-
z = FieldTable[cv[0].downcase]; next
|
|
200
|
+
z = FieldTable[cv[0].downcase]; next if z.nil?
|
|
201
201
|
v[z] ||= cv[2]
|
|
202
202
|
end
|
|
203
203
|
else
|
|
@@ -211,7 +211,7 @@ module Sisimai
|
|
|
211
211
|
|
|
212
212
|
# Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp]
|
|
213
213
|
# has an SPF record with a hard fail
|
|
214
|
-
next
|
|
214
|
+
next if e.start_with?(" ") == false
|
|
215
215
|
v["diagnosis"] += " #{Sisimai::String.sweep(e)}"
|
|
216
216
|
end
|
|
217
217
|
end
|
data/lib/sisimai/rfc3834.rb
CHANGED
|
@@ -28,6 +28,9 @@ module Sisimai
|
|
|
28
28
|
)
|
|
29
29
|
[ ]*(.+)\z
|
|
30
30
|
}x.freeze
|
|
31
|
+
Suspending = [
|
|
32
|
+
["this email inbox", " is no longer in use."],
|
|
33
|
+
].freeze
|
|
31
34
|
|
|
32
35
|
# Detect auto reply message as RFC3834
|
|
33
36
|
# @param [Hash] mhead Message headers of a bounce email
|
|
@@ -41,15 +44,14 @@ module Sisimai
|
|
|
41
44
|
|
|
42
45
|
LowerLabel.each do |e|
|
|
43
46
|
# Set lower-cased value of each header related to auto-response
|
|
44
|
-
next
|
|
47
|
+
next if mhead.has_key?(e) == false
|
|
45
48
|
lower[e] = mhead[e].downcase
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
# DETECT_EXCLUSION_MESSAGE
|
|
49
52
|
DoNotParse.each_key do |e|
|
|
50
53
|
# Exclude message from root@
|
|
51
|
-
next
|
|
52
|
-
next unless DoNotParse[e].any? { |a| lower[e].include?(a) }
|
|
54
|
+
next if lower[e].nil? || DoNotParse[e].none? { |a| lower[e].include?(a) }
|
|
53
55
|
leave = 1
|
|
54
56
|
break
|
|
55
57
|
end
|
|
@@ -58,8 +60,7 @@ module Sisimai
|
|
|
58
60
|
# DETECT_AUTO_REPLY_MESSAGE0
|
|
59
61
|
AutoReply0.each_key do |e|
|
|
60
62
|
# RFC3834 Auto-Submitted and other headers
|
|
61
|
-
next
|
|
62
|
-
next unless AutoReply0[e].any? { |a| lower[e].include?(a) }
|
|
63
|
+
next if lower[e].nil? || AutoReply0[e].none? { |a| lower[e].include?(a) }
|
|
63
64
|
match += 1
|
|
64
65
|
break
|
|
65
66
|
end
|
|
@@ -78,7 +79,7 @@ module Sisimai
|
|
|
78
79
|
# RECIPIENT_ADDRESS
|
|
79
80
|
%w[from return-path].each do |e|
|
|
80
81
|
# Try to get the address of the recipient
|
|
81
|
-
next
|
|
82
|
+
next if mhead[e].nil?
|
|
82
83
|
v['recipient'] = mhead[e]
|
|
83
84
|
break
|
|
84
85
|
end
|
|
@@ -88,12 +89,12 @@ module Sisimai
|
|
|
88
89
|
v['recipient'] = Sisimai::Address.s3s4(v['recipient'])
|
|
89
90
|
recipients += 1
|
|
90
91
|
end
|
|
91
|
-
return nil
|
|
92
|
+
return nil if recipients == 0
|
|
92
93
|
|
|
93
94
|
if mhead['content-type']
|
|
94
95
|
# Get the boundary string and set regular expression for matching with the boundary string.
|
|
95
96
|
q = Sisimai::RFC2045.boundary(mhead['content-type'], 0) || ''
|
|
96
|
-
MarkingsOf[:boundary] = q
|
|
97
|
+
MarkingsOf[:boundary] = q if q.empty? == false
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
# MESSAGE_BODY: Get the vacation message
|
|
@@ -117,6 +118,15 @@ module Sisimai
|
|
|
117
118
|
v['diagnosis'] ||= mhead['subject']
|
|
118
119
|
v['diagnosis'] = Sisimai::String.sweep(v['diagnosis'])
|
|
119
120
|
v['reason'] = 'vacation'
|
|
121
|
+
|
|
122
|
+
cv = v['diagnosis'].downcase
|
|
123
|
+
Suspending.each do |e|
|
|
124
|
+
# Check that the auto-replied message indicates the "Suspend" reason or not.
|
|
125
|
+
next unless Sisimai::String.aligned(cv, e)
|
|
126
|
+
v['reason'] = 'suspend'
|
|
127
|
+
break
|
|
128
|
+
end
|
|
129
|
+
|
|
120
130
|
v['date'] = mhead['date']
|
|
121
131
|
v['status'] = ''
|
|
122
132
|
|
data/lib/sisimai/rfc5322.rb
CHANGED
|
@@ -36,7 +36,7 @@ module Sisimai
|
|
|
36
36
|
# @param [String] argv1 Received header
|
|
37
37
|
# @return [Array] Received header as a structured data
|
|
38
38
|
def received(argv1)
|
|
39
|
-
return []
|
|
39
|
+
return [] if argv1.is_a?(::String) == false
|
|
40
40
|
return [] if argv1.include?(' invoked by uid') || argv1.include?(' invoked from network')
|
|
41
41
|
|
|
42
42
|
# - https://datatracker.ietf.org/doc/html/rfc5322
|
|
@@ -63,14 +63,14 @@ module Sisimai
|
|
|
63
63
|
recvd.each do |e|
|
|
64
64
|
# Look up each label defined in "label" from Received header
|
|
65
65
|
index += 1
|
|
66
|
-
break
|
|
67
|
-
next
|
|
66
|
+
break if index >= range; f = e.downcase
|
|
67
|
+
next if label.none? { |a| f == a }
|
|
68
68
|
token[f] = recvd[index + 1] || next
|
|
69
69
|
token[f] = token[f].downcase.delete('();')
|
|
70
70
|
|
|
71
|
-
next
|
|
72
|
-
break
|
|
73
|
-
next
|
|
71
|
+
next if f != 'from'
|
|
72
|
+
break if index + 2 >= range
|
|
73
|
+
next if recvd[index + 2].start_with?('(') == false
|
|
74
74
|
|
|
75
75
|
# Get and keep a hostname in the comment as follows:
|
|
76
76
|
# from mx1.example.com (c213502.kyoto.example.ne.jp [192.0.2.135]) by mx.example.jp (V8/cf)
|
|
@@ -90,7 +90,7 @@ module Sisimai
|
|
|
90
90
|
|
|
91
91
|
# The 2nd element after the current element is a continuation of the current element.
|
|
92
92
|
# such as "(c213502.kyoto.example.ne.jp", "[192.0.2.135])"
|
|
93
|
-
break
|
|
93
|
+
break if index + 3 >= range
|
|
94
94
|
other << recvd[index + 3].delete('();')
|
|
95
95
|
end
|
|
96
96
|
|
|
@@ -173,7 +173,7 @@ module Sisimai
|
|
|
173
173
|
|
|
174
174
|
cutby.each do |e|
|
|
175
175
|
# Find a boundary string(2nd argument) from the 1st argument
|
|
176
|
-
positionor = email.index(e); next
|
|
176
|
+
positionor = email.index(e); next if positionor.nil?
|
|
177
177
|
boundaryor = e
|
|
178
178
|
break
|
|
179
179
|
end
|
|
@@ -200,7 +200,7 @@ module Sisimai
|
|
|
200
200
|
# Remove text after the first blank line: \n\n when "keeps" is false
|
|
201
201
|
latterpart = latterpart[0, latterpart.index("\n\n")] if latterpart.include?("\n\n")
|
|
202
202
|
end
|
|
203
|
-
latterpart << "\n"
|
|
203
|
+
latterpart << "\n" if latterpart.end_with?("\n") == false
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
return [formerpart, latterpart]
|