sisimai 5.4.0-java → 5.5.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.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codecovio.yml +1 -1
  3. data/.github/workflows/rake-test.yml +1 -1
  4. data/ChangeLog.md +45 -0
  5. data/Makefile +2 -4
  6. data/README-JA.md +23 -20
  7. data/README.md +23 -20
  8. data/lib/sisimai/address.rb +92 -44
  9. data/lib/sisimai/arf.rb +7 -8
  10. data/lib/sisimai/datetime.rb +2 -2
  11. data/lib/sisimai/fact/json.rb +1 -2
  12. data/lib/sisimai/fact/yaml.rb +1 -2
  13. data/lib/sisimai/fact.rb +60 -35
  14. data/lib/sisimai/lda.rb +2 -2
  15. data/lib/sisimai/lhost/activehunter.rb +4 -5
  16. data/lib/sisimai/lhost/amazonses.rb +3 -4
  17. data/lib/sisimai/lhost/apachejames.rb +2 -2
  18. data/lib/sisimai/lhost/biglobe.rb +6 -6
  19. data/lib/sisimai/lhost/courier.rb +7 -7
  20. data/lib/sisimai/lhost/domino.rb +6 -6
  21. data/lib/sisimai/lhost/dragonfly.rb +5 -5
  22. data/lib/sisimai/lhost/einsundeins.rb +4 -4
  23. data/lib/sisimai/lhost/exchange2003.rb +7 -7
  24. data/lib/sisimai/lhost/exchange2007.rb +3 -3
  25. data/lib/sisimai/lhost/exim.rb +7 -7
  26. data/lib/sisimai/lhost/ezweb.rb +3 -2
  27. data/lib/sisimai/lhost/fml.rb +9 -9
  28. data/lib/sisimai/lhost/gmail.rb +9 -9
  29. data/lib/sisimai/lhost/gmx.rb +3 -3
  30. data/lib/sisimai/lhost/googlegroups.rb +6 -7
  31. data/lib/sisimai/lhost/googleworkspace.rb +5 -6
  32. data/lib/sisimai/lhost/imailserver.rb +4 -4
  33. data/lib/sisimai/lhost/kddi.rb +4 -4
  34. data/lib/sisimai/lhost/mailfoundry.rb +3 -3
  35. data/lib/sisimai/lhost/{mailmarshalsmtp.rb → mailmarshal.rb} +5 -5
  36. data/lib/sisimai/lhost/messagingserver.rb +8 -8
  37. data/lib/sisimai/lhost/mfilter.rb +8 -4
  38. data/lib/sisimai/lhost/mimecast.rb +105 -0
  39. data/lib/sisimai/lhost/notes.rb +5 -5
  40. data/lib/sisimai/lhost/opensmtpd.rb +5 -5
  41. data/lib/sisimai/lhost/postfix.rb +38 -32
  42. data/lib/sisimai/lhost/qmail.rb +6 -6
  43. data/lib/sisimai/lhost/sendmail.rb +13 -13
  44. data/lib/sisimai/lhost/{interscanmss.rb → trendmicro.rb} +8 -9
  45. data/lib/sisimai/lhost/v5sendmail.rb +7 -7
  46. data/lib/sisimai/lhost/verizon.rb +3 -3
  47. data/lib/sisimai/lhost/x1.rb +7 -4
  48. data/lib/sisimai/lhost/x2.rb +40 -12
  49. data/lib/sisimai/lhost/x3.rb +3 -3
  50. data/lib/sisimai/lhost/x6.rb +2 -2
  51. data/lib/sisimai/lhost/zoho.rb +5 -5
  52. data/lib/sisimai/lhost.rb +18 -17
  53. data/lib/sisimai/mail/maildir.rb +4 -4
  54. data/lib/sisimai/mail/mbox.rb +4 -4
  55. data/lib/sisimai/mail/memory.rb +1 -1
  56. data/lib/sisimai/mail/stdin.rb +2 -2
  57. data/lib/sisimai/message.rb +34 -34
  58. data/lib/sisimai/order.rb +5 -4
  59. data/lib/sisimai/reason/authfailure.rb +1 -1
  60. data/lib/sisimai/reason/badreputation.rb +1 -1
  61. data/lib/sisimai/reason/blocked.rb +2 -1
  62. data/lib/sisimai/reason/contenterror.rb +1 -2
  63. data/lib/sisimai/reason/exceedlimit.rb +1 -1
  64. data/lib/sisimai/reason/expired.rb +1 -1
  65. data/lib/sisimai/reason/failedstarttls.rb +1 -1
  66. data/lib/sisimai/reason/filtered.rb +1 -1
  67. data/lib/sisimai/reason/hasmoved.rb +1 -1
  68. data/lib/sisimai/reason/hostunknown.rb +2 -2
  69. data/lib/sisimai/reason/mailboxfull.rb +1 -1
  70. data/lib/sisimai/reason/mailererror.rb +1 -1
  71. data/lib/sisimai/reason/mesgtoobig.rb +1 -1
  72. data/lib/sisimai/reason/networkerror.rb +1 -1
  73. data/lib/sisimai/reason/norelaying.rb +2 -2
  74. data/lib/sisimai/reason/notaccept.rb +2 -3
  75. data/lib/sisimai/reason/notcompliantrfc.rb +1 -1
  76. data/lib/sisimai/reason/policyviolation.rb +2 -4
  77. data/lib/sisimai/reason/rejected.rb +5 -3
  78. data/lib/sisimai/reason/requireptr.rb +1 -1
  79. data/lib/sisimai/reason/securityerror.rb +1 -1
  80. data/lib/sisimai/reason/spamdetected.rb +1 -1
  81. data/lib/sisimai/reason/speeding.rb +1 -1
  82. data/lib/sisimai/reason/suspend.rb +2 -1
  83. data/lib/sisimai/reason/systemerror.rb +1 -1
  84. data/lib/sisimai/reason/systemfull.rb +1 -1
  85. data/lib/sisimai/reason/toomanyconn.rb +1 -1
  86. data/lib/sisimai/reason/userunknown.rb +4 -3
  87. data/lib/sisimai/reason/vacation.rb +1 -1
  88. data/lib/sisimai/reason/virusdetected.rb +1 -1
  89. data/lib/sisimai/reason.rb +12 -12
  90. data/lib/sisimai/rfc1123.rb +58 -18
  91. data/lib/sisimai/rfc1894.rb +6 -8
  92. data/lib/sisimai/rfc2045.rb +25 -13
  93. data/lib/sisimai/rfc3464/thirdparty.rb +2 -3
  94. data/lib/sisimai/rfc3464.rb +6 -6
  95. data/lib/sisimai/rfc3834.rb +18 -8
  96. data/lib/sisimai/rfc5322.rb +9 -9
  97. data/lib/sisimai/rfc791.rb +2 -2
  98. data/lib/sisimai/rhost/aol.rb +4 -1
  99. data/lib/sisimai/rhost/apple.rb +11 -7
  100. data/lib/sisimai/rhost/cox.rb +9 -5
  101. data/lib/sisimai/rhost/facebook.rb +9 -3
  102. data/lib/sisimai/rhost/franceptt.rb +86 -37
  103. data/lib/sisimai/rhost/godaddy.rb +10 -1
  104. data/lib/sisimai/rhost/google.rb +26 -22
  105. data/lib/sisimai/rhost/gsuite.rb +1 -1
  106. data/lib/sisimai/rhost/kddi.rb +1 -1
  107. data/lib/sisimai/rhost/messagelabs.rb +160 -2
  108. data/lib/sisimai/rhost/microsoft.rb +80 -29
  109. data/lib/sisimai/rhost/mimecast.rb +30 -21
  110. data/lib/sisimai/rhost/nttdocomo.rb +69 -89
  111. data/lib/sisimai/rhost/outlook.rb +1 -1
  112. data/lib/sisimai/rhost/spectrum.rb +1 -1
  113. data/lib/sisimai/rhost/tencent.rb +5 -4
  114. data/lib/sisimai/rhost/yahooinc.rb +2 -2
  115. data/lib/sisimai/rhost/zoho.rb +72 -0
  116. data/lib/sisimai/rhost.rb +5 -4
  117. data/lib/sisimai/smtp/command.rb +2 -2
  118. data/lib/sisimai/smtp/reply.rb +11 -4
  119. data/lib/sisimai/smtp/status.rb +17 -8
  120. data/lib/sisimai/smtp/transcript.rb +3 -3
  121. data/lib/sisimai/string.rb +6 -10
  122. data/lib/sisimai/version.rb +1 -1
  123. data/lib/sisimai.rb +1 -1
  124. data/set-of-emails/maildir/bsd/lhost-exim-56.eml +40 -40
  125. data/set-of-emails/maildir/bsd/lhost-mfilter-05.eml +41 -0
  126. data/set-of-emails/maildir/bsd/lhost-mimecast-01.eml +46 -0
  127. data/set-of-emails/maildir/bsd/lhost-mimecast-02.eml +59 -0
  128. data/set-of-emails/maildir/bsd/lhost-postfix-79.eml +81 -0
  129. data/set-of-emails/maildir/bsd/lhost-postfix-80.eml +84 -0
  130. data/set-of-emails/maildir/bsd/lhost-x1-03.eml +26 -0
  131. data/set-of-emails/maildir/bsd/lhost-x1-04.eml +45 -0
  132. data/set-of-emails/maildir/bsd/lhost-x2-07.eml +30 -0
  133. data/set-of-emails/maildir/bsd/rfc3464-66.eml +170 -0
  134. data/set-of-emails/maildir/bsd/rfc3834-06.eml +59 -0
  135. data/set-of-emails/maildir/bsd/rhost-apple-05.eml +96 -0
  136. data/set-of-emails/maildir/bsd/rhost-zoho-01.eml +88 -0
  137. data/set-of-emails/maildir/bsd/rhost-zoho-02.eml +86 -0
  138. data/set-of-emails/maildir/bsd/rhost-zoho-03.eml +87 -0
  139. data/set-of-emails/maildir/bsd/rhost-zoho-04.eml +86 -0
  140. data/set-of-emails/maildir/not/rb-issue-368-bug.eml +39 -0
  141. data/sisimai-java.gemspec +1 -1
  142. data/sisimai.gemspec +1 -1
  143. metadata +27 -9
  144. /data/set-of-emails/maildir/bsd/{lhost-mailmarshalsmtp-02.eml → lhost-mailmarshal-02.eml} +0 -0
  145. /data/set-of-emails/maildir/bsd/{lhost-interscanmss-01.eml → lhost-trendmicro-01.eml} +0 -0
  146. /data/set-of-emails/maildir/bsd/{lhost-interscanmss-02.eml → lhost-trendmicro-02.eml} +0 -0
  147. /data/set-of-emails/maildir/bsd/{lhost-interscanmss-03.eml → lhost-trendmicro-03.eml} +0 -0
@@ -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 unless argv1
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
- unless ctxcharset.casecmp('UTF-8') == 0
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 unless argv1 == 'boundary'
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 upperchunk.to_s.empty?
151
- return ['', ''] unless upperchunk.index('Content-Type')
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
- multiparts.shift if multiparts[0].size < 8
228
- multiparts.pop if multiparts[-1].size < 8
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 unless bodyinside.size > 8
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
- unless bodystring.encoding.to_s == 'UTF-8'
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" unless bodystring[-2, 2] == "\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 "" unless argv1
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" => "disabled",
55
+ "inactive-mailbox" => "suspend",
57
56
  "message-expired" => "expired",
58
57
  "no-answer-from-host" => "networkerror",
59
58
  "policy-related" => "policyviolation",
@@ -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 unless Sisimai::Address.is_emailaddress(cv)
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 unless FieldTable[o[0]]
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 unless f == 1
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 unless Sisimai::RFC3464::ThirdParty.is3rdparty(e)
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 unless z
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 unless e.start_with?(" ")
214
+ next if e.start_with?(" ") == false
215
215
  v["diagnosis"] += " #{Sisimai::String.sweep(e)}"
216
216
  end
217
217
  end
@@ -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 unless mhead.has_key?(e)
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 unless lower[e]
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 unless lower[e]
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 unless mhead[e]
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 unless recipients > 0
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 unless q.empty?
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
 
@@ -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 [] unless argv1.is_a?(::String)
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 unless index < range; f = e.downcase
67
- next unless label.any? { |a| f == a }
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 unless f == 'from'
72
- break unless index + 2 < range
73
- next unless recvd[index + 2].start_with?('(')
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 unless index + 3 < range
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 unless positionor
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" unless latterpart.end_with?("\n")
203
+ latterpart << "\n" if latterpart.end_with?("\n") == false
204
204
  end
205
205
 
206
206
  return [formerpart, latterpart]
@@ -31,13 +31,13 @@ module Sisimai
31
31
  ipv4a = []
32
32
  %w|( ) [ ] ,|.each do |e|
33
33
  # Rewrite: "mx.example.jp[192.0.2.1]" => "mx.example.jp 192.0.2.1"
34
- p0 = given.index(e); next unless p0
34
+ p0 = given.index(e); next if p0.nil?
35
35
  given[p0, 1] = ' '
36
36
  end
37
37
 
38
38
  given.split(' ').each do |e|
39
39
  # Find string including an IPv4 address
40
- next unless e.index('.') # IPv4 address must include "." character
40
+ next if e.index('.').nil? # IPv4 address must include "." character
41
41
 
42
42
  lx = e.size; next if lx < 7 || lx > 17 # 0.0.0.0 = 7, [255.255.255.255] = 17
43
43
  cu = 0 # Cursor for seeking each octet of an IPv4 address
@@ -14,6 +14,9 @@ module Sisimai
14
14
  # @param [Sisimai::Fact] argvs Decoded email object
15
15
  # @return [String] The bounce reason for Aol
16
16
  # @since v5.2.0
17
+ # @see
18
+ # - Y!Sender Hub/SMTP Error Codes: https://senders.yahooinc.com/smtp-error-codes/
19
+ # - The MX record of Aol points "mx-aol.mail.gm0.yahoodns.net".
17
20
  def find(argvs)
18
21
  return "" if argvs["diagnosticcode"].empty?
19
22
  issuedcode = argvs["diagnosticcode"]
@@ -21,7 +24,7 @@ module Sisimai
21
24
 
22
25
  MessagesOf.each_key do |e|
23
26
  # Try to match the error message with message patterns defined in $MessagesOf
24
- next unless MessagesOf[e].any? { |a| issuedcode.include?(a) }
27
+ next if MessagesOf[e].none? { |a| issuedcode.include?(a) }
25
28
  reasontext = e
26
29
  break
27
30
  end
@@ -53,10 +53,14 @@ module Sisimai
53
53
  # Try again later - https://support.apple.com/en-us/HT204137
54
54
  'due to excessive volume',
55
55
  ],
56
+ 'suspend' => [
57
+ # - https://support.apple.com/guide/icloud/stop-using-or-reactivate-addresses-mm3adb030cbf/icloud
58
+ # - 550 5.1.1 <****@icloud.com>: inactive email address (in reply to RCPT TO command)
59
+ "inactive email address",
60
+ ],
56
61
  'userunknown' => [
57
62
  # - 550 5.1.1 <****@icloud.com>: inactive email address (in reply to RCPT TO command)
58
63
  # - 550 5.1.1 unknown or illegal alias: ****@icloud.com
59
- 'inactive email address',
60
64
  'user does not exist',
61
65
  'unknown or illegal alias',
62
66
  ],
@@ -65,20 +69,20 @@ module Sisimai
65
69
  # Detect bounce reason from Apple iCloud Mail
66
70
  # @param [Sisimai::Fact] argvs Decoded email object
67
71
  # @return [String] The bounce reason for Apple
68
- # @see https://support.apple.com/en-us/102322
69
- # https://www.postmastery.com/icloud-postmastery-page/
70
- # https://smtpfieldmanual.com/provider/apple
72
+ # @see
73
+ # - Postmaster information for iCloud Mail: https://support.apple.com/en-us/102322
74
+ # - https://www.postmastery.com/icloud-postmastery-page/
75
+ # - https://smtpfieldmanual.com/provider/apple
71
76
  # @since v5.1.0
72
77
  def find(argvs)
73
- return '' unless argvs
74
- return '' unless argvs['diagnosticcode'].size > 0
78
+ return '' if argvs.nil? || argvs['diagnosticcode'].size == 0
75
79
 
76
80
  issuedcode = argvs['diagnosticcode'].downcase
77
81
  reasontext = ''
78
82
 
79
83
  MessagesOf.each_key do |e|
80
84
  MessagesOf[e].each do |f|
81
- next unless issuedcode.include?(f)
85
+ next if issuedcode.include?(f) == false
82
86
  reasontext = e
83
87
  break
84
88
  end
@@ -61,7 +61,7 @@ module Sisimai
61
61
  'IPBL0101' => 'blocked', # The sending IP is in the Spamhaus Zen and Invaluement ivmSIP DNSBLs.
62
62
  'IPBL0110' => 'blocked', # The sending IP is in the Return Path and Invaluement ivmSIP DNSBLs.
63
63
  'IPBL0111' => 'blocked', # The sending IP is in the Spamhaus Zen, Return Path and Invaluement ivmSIP DNSBLs.
64
- 'IPBL1000' => 'blocked', # The sending IP address is listed on a CSI blacklist. You can check your status on the CSI website.
64
+ 'IPBL1000' => 'blocked', # The sending IP address is listed on a CSI blacklist.
65
65
  'IPBL1001' => 'blocked', # The sending IP is listed in the Cloudmark CSI and Spamhaus Zen DNSBLs.
66
66
  'IPBL1010' => 'blocked', # The sending IP is listed in the Cloudmark CSI and Return Path DNSBLs.
67
67
  'IPBL1011' => 'blocked', # The sending IP is in the Cloudmark CSI, Spamhaus Zen and Return Path DNSBLs.
@@ -69,12 +69,12 @@ module Sisimai
69
69
  'IPBL1101' => 'blocked', # The sending IP is in the Cloudmark CSI, Spamhaus Zen and Invaluement IVMsip DNSBLs.
70
70
  'IPBL1110' => 'blocked', # The sending IP is in the Cloudmark CSI, Return Path and Invaluement ivmSIP DNSBLs.
71
71
  'IPBL1111' => 'blocked', # The sending IP is in the Cloudmark CSI, Spamhaus Zen, Return Path and Invaluement ivmSIP DNSBLs.
72
- 'IPBL00001' => 'blocked', # The sending IP address is listed on a Spamhaus blacklist. Check your status at Spamhaus.
72
+ 'IPBL00001' => 'blocked', # The sending IP address is listed on a Spamhaus blacklist.
73
73
 
74
74
  'URLBL011' => 'spamdetected', # A URL within the body of the message was found on blocklists SURBL and Spamhaus DBL.
75
75
  'URLBL101' => 'spamdetected', # A URL within the body of the message was found on blocklists SURBL and ivmURI.
76
76
  'URLBL110' => 'spamdetected', # A URL within the body of the message was found on blocklists Spamhaus DBL and ivmURI.
77
- 'URLBL1001' => 'spamdetected', # The URL is listed on a Spamhaus blacklist. Check your status at Spamhaus.
77
+ 'URLBL1001' => 'spamdetected', # The URL is listed on a Spamhaus blacklist.
78
78
  }.freeze
79
79
  MessagesOf = {
80
80
  'blocked' => [
@@ -82,6 +82,8 @@ module Sisimai
82
82
  # a three-hour block of the client's IP address.
83
83
  # - The sending IP address has exceeded the threshold of invalid recipients and has
84
84
  # been blocked.
85
+ # - Cox enforces various rate limits to protect our platform. The sending IP address
86
+ # has exceeded one of these rate limits and has been temporarily blocked.
85
87
  'cox too many bad commands from',
86
88
  'too many invalid recipients',
87
89
  ],
@@ -127,7 +129,9 @@ module Sisimai
127
129
  # Detect bounce reason from https://cox.com/
128
130
  # @param [Sisimai::Fact] argvs Decoded email object
129
131
  # @return [String, Nil] The bounce reason at Cox
130
- # @see https://www.cox.com/residential/support/email-error-codes.html
132
+ # @see
133
+ # - Email Error Codes: https://www.cox.com/business/support/email-error-codes.html
134
+ # - Feedback Loop Service https://www.cox.com/business/support/feedback-loop-service.html
131
135
  # @since v4.25.8
132
136
  def find(argvs)
133
137
  return "" if argvs["diagnosticcode"].empty?
@@ -145,7 +149,7 @@ module Sisimai
145
149
  # The error code was not found in ErrorCodes
146
150
  MessagesOf.each_key do |e|
147
151
  # Try to find with each error message defined in MessagesOf
148
- next unless MessagesOf[e].any? { |a| issuedcode.include?(a) }
152
+ next if MessagesOf[e].none? { |a| issuedcode.include?(a) }
149
153
  reasontext = e
150
154
  break
151
155
  end
@@ -27,6 +27,9 @@ module Sisimai
27
27
  "RCP-P2", # The attempted recipient's preferences prevent messages from being delivered.
28
28
  "RCP-P3", # The attempted recipient's privacy settings blocked the delivery.
29
29
  ],
30
+ "mailboxfull" => [
31
+ "INT-P7", # The attempted recipient has exceeded their storage quota.
32
+ ],
30
33
  "mesgtoobig" => [
31
34
  "MSG-P1", # The message exceeds Facebook's maximum allowed size.
32
35
  "INT-P2", # The message exceeds Facebook's maximum allowed size.
@@ -34,6 +37,9 @@ module Sisimai
34
37
  "notcompliantrfc" => [
35
38
  "MSG-P3", # The message contains multiple instances of a header field that can only be present once.
36
39
  ],
40
+ "policyviolation" => [
41
+ "POL-P8", # The message does not comply with Facebook's abuse policies and will not be accepted.
42
+ ],
37
43
  "rejected" => [
38
44
  "DNS-P1", # Your SMTP MAIL FROM domain does not exist.
39
45
  "DNS-P2", # Your SMTP MAIL FROM domain does not have an MX record.
@@ -45,13 +51,13 @@ module Sisimai
45
51
  ],
46
52
  "spamdetected" => [
47
53
  "POL-P6", # The message contains a url that has been blocked by Facebook.
48
- "POL-P7", # The message does not comply with Facebook's abuse policies and will not be accepted.
49
54
  ],
50
55
  "suspend" => [
51
56
  "RCP-T4", # The attempted recipient address is currently deactivated. The user may or may not reactivate it.
52
57
  ],
53
58
  "systemerror" => [
54
- "RCP-T1", # The attempted recipient address is not currently available due to an internal system issue. This is a temporary condition.
59
+ "RCP-T1", # The attempted recipient address is not currently available due to an internal system issue.
60
+ "INT-Tx", # These codes indicate a temporary issue internal to Facebook's system.
55
61
  ],
56
62
  "toomanyconn" => [
57
63
  "CON-T1", # Facebook's mail server currently has too many connections open to allow another one.
@@ -85,7 +91,7 @@ module Sisimai
85
91
 
86
92
  ErrorCodes.each_key do |e|
87
93
  # The key is a bounce reason name
88
- next unless ErrorCodes[e].any? { |a| errorlabel == a }
94
+ next if ErrorCodes[e].none? { |a| errorlabel == a }
89
95
  reasontext = e
90
96
  break
91
97
  end