sisimai 5.3.0-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/ChangeLog.md +12 -0
- data/Makefile +3 -2
- data/README-JA.md +2 -2
- data/README.md +6 -6
- 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 +11 -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/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 +3 -5
- data/lib/sisimai/smtp/status.rb +14 -24
- 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 +3 -2
data/lib/sisimai/rfc1894.rb
CHANGED
@@ -57,9 +57,9 @@ module Sisimai
|
|
57
57
|
#"text" => ["X-Original-Message-ID", "Final-Log-ID", "Original-Envelope-ID"],
|
58
58
|
}.freeze
|
59
59
|
|
60
|
-
SubtypeSet = {
|
60
|
+
SubtypeSet = {"addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS"}.freeze
|
61
61
|
ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
|
62
|
-
Correction = {
|
62
|
+
Correction = {'deliverable' => 'delivered', 'expired' => 'delayed', 'failure' => 'failed'}
|
63
63
|
FieldGroup = {
|
64
64
|
'original-recipient' => 'addr',
|
65
65
|
'final-recipient' => 'addr',
|
@@ -108,9 +108,8 @@ module Sisimai
|
|
108
108
|
# 2: Matched with per-recipient field
|
109
109
|
# @since v4.25.0
|
110
110
|
def match(argv0 = '')
|
111
|
-
return 0
|
112
|
-
return 0
|
113
|
-
label = Sisimai::RFC1894.label(argv0); return 0 unless label
|
111
|
+
return 0 if argv0.to_s == ""
|
112
|
+
label = Sisimai::RFC1894.label(argv0); return 0 if label.empty?
|
114
113
|
match = 0
|
115
114
|
|
116
115
|
FieldNames[0].each_key do |e|
|
@@ -135,7 +134,7 @@ module Sisimai
|
|
135
134
|
# @return [String] Field name as a label
|
136
135
|
# @since v4.25.15
|
137
136
|
def label(argv0 = '')
|
138
|
-
return
|
137
|
+
return "" if argv0.empty?
|
139
138
|
return argv0.split(':', 2).shift.downcase
|
140
139
|
end
|
141
140
|
|
data/lib/sisimai/rfc2045.rb
CHANGED
@@ -7,10 +7,9 @@ module Sisimai
|
|
7
7
|
|
8
8
|
# Check that the argument is MIME-Encoded string or not
|
9
9
|
# @param [String] argvs String to be checked
|
10
|
-
# @return [
|
11
|
-
# true: MIME encoded string
|
10
|
+
# @return [Boolean] false: Not MIME encoded string, true: MIME encoded string
|
12
11
|
def is_encoded(argv1)
|
13
|
-
return
|
12
|
+
return false unless argv1
|
14
13
|
|
15
14
|
text1 = argv1.delete('"')
|
16
15
|
mime1 = false
|
@@ -60,13 +59,12 @@ module Sisimai
|
|
60
59
|
textblocks[-1].gsub!(/\r\n/, '')
|
61
60
|
textblocks << cv[5]
|
62
61
|
else
|
63
|
-
textblocks << if textblocks.empty? then e else
|
62
|
+
textblocks << if textblocks.empty? then e else " #{e}" end
|
64
63
|
end
|
65
64
|
end
|
66
|
-
|
67
65
|
return '' if textblocks.empty?
|
68
|
-
p = textblocks.join('')
|
69
66
|
|
67
|
+
p = textblocks.join('')
|
70
68
|
if ctxcharset && qbencoding
|
71
69
|
# utf8 => UTF-8
|
72
70
|
ctxcharset = 'UTF-8' if ctxcharset.casecmp('UTF8') == 0
|
@@ -74,32 +72,32 @@ module Sisimai
|
|
74
72
|
unless ctxcharset.casecmp('UTF-8') == 0
|
75
73
|
# Characterset is not UTF-8
|
76
74
|
begin
|
77
|
-
p .encode!('UTF-8', ctxcharset)
|
75
|
+
p = p.encode!('UTF-8', ctxcharset)
|
78
76
|
rescue
|
79
77
|
p = 'FAILED TO CONVERT THE SUBJECT'
|
80
78
|
end
|
81
79
|
end
|
82
80
|
end
|
83
|
-
|
84
|
-
return
|
81
|
+
q = p.dup
|
82
|
+
return q.force_encoding('UTF-8').scrub('?')
|
85
83
|
end
|
86
84
|
|
87
85
|
# Decode MIME BASE64 Encoded string
|
88
86
|
# @param [String] argv0 MIME Encoded text
|
89
87
|
# @return [String] MIME-Decoded text
|
90
88
|
def decodeB(argv0 = nil)
|
91
|
-
return nil
|
89
|
+
return "" if argv0.nil? || argv0.empty?
|
92
90
|
|
93
91
|
p = nil
|
94
92
|
if cv = argv0.match(%r|([+/\=0-9A-Za-z\r\n]+)|) then p = Base64.decode64(cv[1]) end
|
95
|
-
return p ? p.scrub('?') :
|
93
|
+
return p ? p.scrub('?') : ""
|
96
94
|
end
|
97
95
|
|
98
96
|
# Decode MIME Quoted-Printable Encoded string
|
99
97
|
# @param [String] argv0 MIME Encoded text
|
100
98
|
# @return [String] MIME Decoded text
|
101
99
|
def decodeQ(argv0 = nil)
|
102
|
-
return nil
|
100
|
+
return "" if argv0.nil? || argv0.empty?
|
103
101
|
return argv0.unpack('M').first.scrub('?')
|
104
102
|
end
|
105
103
|
|
@@ -109,7 +107,7 @@ module Sisimai
|
|
109
107
|
# @return [String] The value of the parameter
|
110
108
|
# @since v5.0.0
|
111
109
|
def parameter(argv0 = '', argv1 = '')
|
112
|
-
return
|
110
|
+
return "" if argv0.empty?
|
113
111
|
parameterq = argv1.size > 0 ? argv1 + '=' : ''
|
114
112
|
paramindex = argv1.size > 0 ? argv0.index(parameterq) : 0
|
115
113
|
return '' unless paramindex
|
@@ -128,7 +126,7 @@ module Sisimai
|
|
128
126
|
# 1: End of boundary
|
129
127
|
# @return [String] Boundary string
|
130
128
|
def boundary(argv0 = '', start = -1)
|
131
|
-
return
|
129
|
+
return "" if argv0.empty?
|
132
130
|
btext = parameter(argv0, 'boundary')
|
133
131
|
return '' if btext.empty?
|
134
132
|
|
@@ -175,8 +173,8 @@ module Sisimai
|
|
175
173
|
elsif e.index('boundary=') || e.index('charset=')
|
176
174
|
# "Content-Type" field has boundary="..." or charset="utf-8"
|
177
175
|
next if headerpart[0].empty?
|
178
|
-
headerpart[0]
|
179
|
-
headerpart[0].gsub
|
176
|
+
headerpart[0] += " #{e}"
|
177
|
+
headerpart[0] = headerpart[0].gsub(/\s\s+/, ' ')
|
180
178
|
end
|
181
179
|
end
|
182
180
|
return headerpart if heads
|
@@ -197,7 +195,7 @@ module Sisimai
|
|
197
195
|
break if mediatypev.index('/feedback-report')
|
198
196
|
break if ctencoding.empty?
|
199
197
|
|
200
|
-
multipart1[2]
|
198
|
+
multipart1[2] += sprintf("Content-Transfer-Encoding: %s\n", ctencoding)
|
201
199
|
break
|
202
200
|
end
|
203
201
|
|
@@ -206,10 +204,10 @@ module Sisimai
|
|
206
204
|
break if lowerchunk.empty?
|
207
205
|
break if lowerchunk[0, 1] == "\n"
|
208
206
|
|
209
|
-
multipart1[2]
|
207
|
+
multipart1[2] += "\n"
|
210
208
|
break
|
211
209
|
end
|
212
|
-
multipart1[2]
|
210
|
+
multipart1[2] += lowerchunk
|
213
211
|
return multipart1
|
214
212
|
end
|
215
213
|
|
@@ -264,10 +262,8 @@ module Sisimai
|
|
264
262
|
# @param [String] argv1 A pointer to multipart/* message blocks
|
265
263
|
# @return [String] Message body
|
266
264
|
def makeflat(argv0 = '', argv1 = '')
|
267
|
-
return nil
|
268
|
-
return
|
269
|
-
return '' unless argv0.index('multipart/')
|
270
|
-
return '' unless argv0.index('boundary=')
|
265
|
+
return "" if argv0.nil? || argv1.nil?
|
266
|
+
return "" if argv0.index('multipart/') == false || argv0.index('boundary=') == false
|
271
267
|
|
272
268
|
# Some bounce messages include lower-cased "content-type:" field such as the followings:
|
273
269
|
# - content-type: message/delivery-status => Content-Type: message/delivery-status
|
@@ -284,7 +280,7 @@ module Sisimai
|
|
284
280
|
# - text/plain, text/rfc822-headers
|
285
281
|
# - message/delivery-status, message/rfc822, message/partial, message/feedback-report
|
286
282
|
istexthtml = false
|
287
|
-
mediatypev = parameter(e[0])
|
283
|
+
mediatypev = parameter(e[0]); mediatypev = "text/plain" if mediatypev.empty?
|
288
284
|
next if mediatypev.start_with?('text/', 'message/') == false
|
289
285
|
|
290
286
|
if mediatypev == 'text/html'
|
@@ -312,7 +308,7 @@ module Sisimai
|
|
312
308
|
# Content-Transfer-Encoding: 7bit
|
313
309
|
if cv = e[0].downcase.match(iso2022set)
|
314
310
|
# Content-Type: text/plain; charset=ISO-2022-JP
|
315
|
-
bodystring = Sisimai::String.to_utf8(bodyinside, cv[1])
|
311
|
+
bodystring = Sisimai::String.to_utf8(bodyinside, cv[1])
|
316
312
|
else
|
317
313
|
# No "charset" parameter in the value of Content-Type: header
|
318
314
|
bodystring = bodyinside
|
@@ -324,7 +320,7 @@ module Sisimai
|
|
324
320
|
|
325
321
|
if istexthtml
|
326
322
|
# Try to delete HTML tags inside of text/html part whenever possible
|
327
|
-
bodystring = Sisimai::String.to_plain(bodystring)
|
323
|
+
bodystring = Sisimai::String.to_plain(bodystring)
|
328
324
|
end
|
329
325
|
next if bodystring.empty?
|
330
326
|
|
@@ -341,16 +337,16 @@ module Sisimai
|
|
341
337
|
bodystring.scrub!('?')
|
342
338
|
else
|
343
339
|
# ISO-8859-1, GB2312, and so on
|
344
|
-
bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset)
|
340
|
+
bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset)
|
345
341
|
end
|
346
|
-
bodystring
|
342
|
+
bodystring += "\n\n"
|
347
343
|
end
|
348
344
|
|
349
345
|
bodystring.gsub!(/\r\n/, "\n") if bodystring.include?("\r\n") # Convert CRLF to LF
|
350
346
|
|
351
347
|
else
|
352
348
|
# There is no Content-Transfer-Encoding header in the part
|
353
|
-
bodystring
|
349
|
+
bodystring += bodyinside
|
354
350
|
end
|
355
351
|
|
356
352
|
if delimiters.any? { |a| mediatypev.include?(a) }
|
@@ -361,8 +357,8 @@ module Sisimai
|
|
361
357
|
end
|
362
358
|
|
363
359
|
# Append "\n" when the last character of $bodystring is not LF
|
364
|
-
bodystring
|
365
|
-
flattenout
|
360
|
+
bodystring += "\n\n" unless bodystring[-2, 2] == "\n\n"
|
361
|
+
flattenout += bodystring
|
366
362
|
end
|
367
363
|
|
368
364
|
return flattenout
|
@@ -39,7 +39,7 @@ module Sisimai
|
|
39
39
|
def xfield(argv1 = "")
|
40
40
|
return [] if argv1.nil? || argv1.empty?
|
41
41
|
party = Sisimai::RFC3464::ThirdParty.returnedby(argv1); return [] if party.empty?
|
42
|
-
return Module.const_get("Sisimai::RFC3464::ThirdParty
|
42
|
+
return Module.const_get("Sisimai::RFC3464::ThirdParty::#{party}").xfield(argv1)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
data/lib/sisimai/rfc3464.rb
CHANGED
@@ -20,7 +20,7 @@ module Sisimai
|
|
20
20
|
"Content-Type: message/partial",
|
21
21
|
"Content-Disposition: inline", # See lhost-amavis-*.eml, lhost-facebook-*.eml
|
22
22
|
].freeze
|
23
|
-
StartingOf = {
|
23
|
+
StartingOf = {message: ["Content-Type: message/delivery-status"]}.freeze
|
24
24
|
FieldTable = Sisimai::RFC1894.FIELDTABLE
|
25
25
|
|
26
26
|
# Decode a bounce mail which have fields defined in RFC3464
|
@@ -37,7 +37,7 @@ module Sisimai
|
|
37
37
|
end
|
38
38
|
|
39
39
|
permessage = {}
|
40
|
-
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
40
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil
|
41
41
|
alternates = Sisimai::Lhost.DELIVERYSTATUS
|
42
42
|
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
43
43
|
readslices = [""]
|
@@ -46,7 +46,6 @@ module Sisimai
|
|
46
46
|
beforemesg = "" # (String) String before StartingOf[:message]
|
47
47
|
goestonext = false # (Bool) Flag: do not append the line into beforemesg
|
48
48
|
isboundary = [Sisimai::RFC2045.boundary(mhead["content-type"], 0)]; isboundary[0] ||= ""
|
49
|
-
v = nil
|
50
49
|
|
51
50
|
while emailparts[0].index('@').nil? do
|
52
51
|
# There is no email address in the first element of emailparts
|
@@ -134,12 +133,11 @@ module Sisimai
|
|
134
133
|
break if e.start_with?("--") # Boundary string
|
135
134
|
break if e.include?("--- The follow") # ----- The following addresses had delivery problems -----
|
136
135
|
break if e.include?("--- Transcript") # ----- Transcript of session follows -----
|
137
|
-
beforemesg
|
136
|
+
beforemesg += "#{e} "; break
|
138
137
|
end
|
139
138
|
next
|
140
139
|
end
|
141
|
-
next if (readcursor & Indicators[:deliverystatus]) == 0
|
142
|
-
next if e.empty?
|
140
|
+
next if (readcursor & Indicators[:deliverystatus]) == 0 || e.empty?
|
143
141
|
|
144
142
|
f = Sisimai::RFC1894.match(e)
|
145
143
|
if f > 0
|
@@ -177,7 +175,7 @@ module Sisimai
|
|
177
175
|
# There are other error messages as a comment such as the following:
|
178
176
|
# Status: 5.0.0 (permanent failure)
|
179
177
|
# Status: 4.0.0 (cat.example.net: host name lookup failure)
|
180
|
-
v["diagnosis"]
|
178
|
+
v["diagnosis"] += " #{o[4]} "
|
181
179
|
end
|
182
180
|
next unless FieldTable[o[0]]
|
183
181
|
next if o[3] == "host" && Sisimai::RFC1123.is_internethost(o[2]) == false
|
@@ -208,13 +206,13 @@ module Sisimai
|
|
208
206
|
# In the case of multiple "message/delivery-status" line
|
209
207
|
next if e.start_with?("Content-") # Content-Disposition:, ...
|
210
208
|
next if e.start_with?("--") # Boundary string
|
211
|
-
beforemesg
|
209
|
+
beforemesg += "#{e} "; next
|
212
210
|
end
|
213
211
|
|
214
212
|
# Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp]
|
215
213
|
# has an SPF record with a hard fail
|
216
214
|
next unless e.start_with?(" ")
|
217
|
-
v["diagnosis"]
|
215
|
+
v["diagnosis"] += " #{Sisimai::String.sweep(e)}"
|
218
216
|
end
|
219
217
|
end
|
220
218
|
end
|
data/lib/sisimai/rfc3834.rb
CHANGED
@@ -3,7 +3,7 @@ module Sisimai
|
|
3
3
|
module RFC3834
|
4
4
|
class << self
|
5
5
|
# http://tools.ietf.org/html/rfc3834
|
6
|
-
MarkingsOf = {
|
6
|
+
MarkingsOf = {:boundary => '__SISIMAI_PSEUDO_BOUNDARY__'}
|
7
7
|
LowerLabel = %w[from to subject auto-submitted precedence x-apple-action].freeze
|
8
8
|
DoNotParse = {
|
9
9
|
'from' => ['root@', 'postmaster@', 'mailer-daemon@'],
|
@@ -66,7 +66,7 @@ module Sisimai
|
|
66
66
|
return nil if match < 1
|
67
67
|
|
68
68
|
require 'sisimai/lhost'
|
69
|
-
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
69
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = dscontents[-1]
|
70
70
|
bodyslices = mbody.scrub('?').split("\n")
|
71
71
|
rfc822part = '' # (String) message/rfc822-headers part
|
72
72
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
@@ -74,13 +74,11 @@ module Sisimai
|
|
74
74
|
haveloaded = 0 # (Integer) The number of lines loaded from message body
|
75
75
|
blanklines = 0 # (Integer) Counter for countinuous blank lines
|
76
76
|
countuntil = 1 # (Integer) Maximum value of blank lines in the body part
|
77
|
-
v = dscontents[-1]
|
78
77
|
|
79
78
|
# RECIPIENT_ADDRESS
|
80
79
|
%w[from return-path].each do |e|
|
81
80
|
# Try to get the address of the recipient
|
82
81
|
next unless mhead[e]
|
83
|
-
next unless mhead[e]
|
84
82
|
v['recipient'] = mhead[e]
|
85
83
|
break
|
86
84
|
end
|
@@ -109,12 +107,10 @@ module Sisimai
|
|
109
107
|
break if blanklines > countuntil
|
110
108
|
next
|
111
109
|
end
|
112
|
-
next
|
113
|
-
next if e.start_with?('Content-Type')
|
114
|
-
next if e.start_with?('Content-Transfer')
|
110
|
+
next if !e.include?(' ') || e.start_with?('Content-Type', 'Content-Transfer')
|
115
111
|
|
116
112
|
v['diagnosis'] ||= ''
|
117
|
-
v['diagnosis']
|
113
|
+
v['diagnosis'] += "#{e }"
|
118
114
|
haveloaded += 1
|
119
115
|
break if haveloaded >= maxmsgline
|
120
116
|
end
|
@@ -126,7 +122,7 @@ module Sisimai
|
|
126
122
|
|
127
123
|
if cv = lower['subject'].match(SubjectSet)
|
128
124
|
# Get the Subject header from the original message
|
129
|
-
rfc822part =
|
125
|
+
rfc822part = "Subject: #{cv[1]}\n"
|
130
126
|
end
|
131
127
|
return { 'ds' => dscontents, 'rfc822' => rfc822part }
|
132
128
|
end
|
data/lib/sisimai/rfc5322.rb
CHANGED
@@ -32,19 +32,12 @@ module Sisimai
|
|
32
32
|
return []
|
33
33
|
end
|
34
34
|
|
35
|
-
# Fields that might be long
|
36
|
-
# @return [Hash] Long filed(email header) list
|
37
|
-
def LONGFIELDS
|
38
|
-
return { 'to' => true, 'from' => true, 'subject' => true, 'message-id' => true }
|
39
|
-
end
|
40
|
-
|
41
35
|
# Convert Received headers to a structured data
|
42
36
|
# @param [String] argv1 Received header
|
43
37
|
# @return [Array] Received header as a structured data
|
44
38
|
def received(argv1)
|
45
39
|
return [] unless argv1.is_a?(::String)
|
46
|
-
return [] if argv1.include?(' invoked by uid')
|
47
|
-
return [] if argv1.include?(' invoked from network')
|
40
|
+
return [] if argv1.include?(' invoked by uid') || argv1.include?(' invoked from network')
|
48
41
|
|
49
42
|
# - https://datatracker.ietf.org/doc/html/rfc5322
|
50
43
|
# received = "Received:" *received-token ";" date-time CRLF
|
@@ -105,41 +98,30 @@ module Sisimai
|
|
105
98
|
# Check alternatives in "other", and then delete uninformative values.
|
106
99
|
next if e.nil?
|
107
100
|
next if e.size < 4
|
108
|
-
next if e == 'unknown'
|
109
|
-
next if e == '
|
110
|
-
next if e == '[127.0.0.1]'
|
111
|
-
next if e == '[IPv6:::1]'
|
112
|
-
next unless e.include?('.')
|
113
|
-
next if e.include?('=')
|
101
|
+
next if e == 'unknown' || e == 'localhost' || e == '[127.0.0.1]' || e == '[IPv6:::1]'
|
102
|
+
next if e.include?('.') == false || e.include?('=') == true
|
114
103
|
alter << e
|
115
104
|
end
|
116
105
|
|
117
106
|
%w[from by].each do |e|
|
118
107
|
# Remove square brackets from the IP address such as "[192.0.2.25]"
|
119
|
-
next if token[e].
|
120
|
-
next if token[e].empty?
|
121
|
-
next unless token[e].start_with?('[')
|
108
|
+
next if token[e].to_s.empty? || token[e].start_with?('[') == false
|
122
109
|
token[e] = Sisimai::RFC791.find(token[e]).shift || ''
|
123
110
|
end
|
124
111
|
token['from'] ||= ''
|
125
112
|
|
126
113
|
while true do
|
127
114
|
# Prefer hostnames over IP addresses, except for localhost.localdomain and similar.
|
128
|
-
break if token['from'] == 'localhost'
|
129
|
-
break if token['from'] == '
|
130
|
-
break unless token['from'].include?('.') # A hostname without a domain name
|
131
|
-
break unless Sisimai::RFC791.find(token['from']).empty?
|
115
|
+
break if token['from'] == 'localhost' || token['from'] == 'localhost.localdomain'
|
116
|
+
break if token['from'].include?('.') == false || Sisimai::RFC791.find(token['from']).empty? == false
|
132
117
|
|
133
|
-
# No need to rewrite token['from']
|
134
|
-
right = true
|
118
|
+
right = true # No need to rewrite token['from']
|
135
119
|
break
|
136
120
|
end
|
137
121
|
|
138
122
|
while true do
|
139
123
|
# Try to rewrite uninformative hostnames and IP addresses in token['from']
|
140
|
-
break if right
|
141
|
-
break if alter.empty? # There is no alternative to rewriting
|
142
|
-
break if alter[0].include?(token['from'])
|
124
|
+
break if right || alter.empty? || alter[0].include?(token['from'])
|
143
125
|
|
144
126
|
if token['from'].start_with?('localhost')
|
145
127
|
# localhost or localhost.localdomain
|
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
@@ -109,7 +109,7 @@ module Sisimai
|
|
109
109
|
"550", "552", "553", "551", "521", "525", "523", "524", "530", "533", "534", "535", "538",
|
110
110
|
"555", "556", "554", "500", "501", "502", "503", "504",
|
111
111
|
].freeze
|
112
|
-
CodeOfSMTP = {
|
112
|
+
CodeOfSMTP = {'2' => ReplyCode2, '4' => ReplyCode4, '5' => ReplyCode5}.freeze
|
113
113
|
Associated = {
|
114
114
|
"422" => ["AUTH", "4.7.12", "securityerror"], # RFC5238
|
115
115
|
"432" => ["AUTH", "4.7.12", "securityerror"], # RFC4954, RFC5321
|
@@ -163,11 +163,9 @@ module Sisimai
|
|
163
163
|
# Get SMTP Reply Code from the given string
|
164
164
|
# @param [String] argv1 String including SMTP Reply Code like 550
|
165
165
|
# @param [String] argv2 Status code like 5.1.1 or 2 or 4 or 5
|
166
|
-
# @return [String] SMTP Reply Code
|
167
|
-
# [Nil] The first argument did not include SMTP Reply Code value
|
166
|
+
# @return [String] SMTP Reply Code or an empty string
|
168
167
|
def find(argv1 = '', argv2 = '0')
|
169
|
-
return "" if argv1.to_s.size < 3
|
170
|
-
return "" if argv1.upcase.include?('X-UNIX')
|
168
|
+
return "" if argv1.to_s.size < 3 || argv1.upcase.include?('X-UNIX')
|
171
169
|
|
172
170
|
esmtperror = ' ' + argv1 + ' '
|
173
171
|
esmtpreply = ''
|