sisimai 5.6.0-java → 5.7.1-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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +0 -4
  3. data/ChangeLog.md +47 -0
  4. data/README-JA.md +19 -16
  5. data/README.md +23 -21
  6. data/lib/sisimai/address.rb +8 -31
  7. data/lib/sisimai/arf.rb +5 -6
  8. data/lib/sisimai/eb.rb +116 -0
  9. data/lib/sisimai/fact.rb +33 -51
  10. data/lib/sisimai/lda.rb +20 -18
  11. data/lib/sisimai/lhost/activehunter.rb +0 -2
  12. data/lib/sisimai/lhost/amazonses.rb +17 -18
  13. data/lib/sisimai/lhost/apachejames.rb +0 -1
  14. data/lib/sisimai/lhost/biglobe.rb +0 -16
  15. data/lib/sisimai/lhost/courier.rb +9 -7
  16. data/lib/sisimai/lhost/deutschetelekom.rb +120 -0
  17. data/lib/sisimai/lhost/domino.rb +2 -4
  18. data/lib/sisimai/lhost/dragonfly.rb +0 -27
  19. data/lib/sisimai/lhost/einsundeins.rb +4 -12
  20. data/lib/sisimai/lhost/exchange2003.rb +12 -11
  21. data/lib/sisimai/lhost/exchange2007.rb +17 -17
  22. data/lib/sisimai/lhost/exim.rb +34 -81
  23. data/lib/sisimai/lhost/ezweb.rb +15 -51
  24. data/lib/sisimai/lhost/fml.rb +8 -32
  25. data/lib/sisimai/lhost/gmail.rb +10 -32
  26. data/lib/sisimai/lhost/gmx.rb +7 -24
  27. data/lib/sisimai/lhost/googlegroups.rb +6 -5
  28. data/lib/sisimai/lhost/googleworkspace.rb +2 -5
  29. data/lib/sisimai/lhost/imailserver.rb +4 -9
  30. data/lib/sisimai/lhost/kddi.rb +8 -21
  31. data/lib/sisimai/lhost/mailfoundry.rb +0 -2
  32. data/lib/sisimai/lhost/mailmarshal.rb +1 -3
  33. data/lib/sisimai/lhost/messagingserver.rb +4 -15
  34. data/lib/sisimai/lhost/mfilter.rb +0 -1
  35. data/lib/sisimai/lhost/mimecast.rb +0 -1
  36. data/lib/sisimai/lhost/notes.rb +3 -3
  37. data/lib/sisimai/lhost/opensmtpd.rb +0 -40
  38. data/lib/sisimai/lhost/postfix.rb +14 -14
  39. data/lib/sisimai/lhost/qmail.rb +23 -89
  40. data/lib/sisimai/lhost/sendmail.rb +8 -7
  41. data/lib/sisimai/lhost/trendmicro.rb +5 -4
  42. data/lib/sisimai/lhost/v5sendmail.rb +2 -2
  43. data/lib/sisimai/lhost/verizon.rb +4 -4
  44. data/lib/sisimai/lhost/x1.rb +1 -2
  45. data/lib/sisimai/lhost/x2.rb +0 -2
  46. data/lib/sisimai/lhost/x3.rb +4 -9
  47. data/lib/sisimai/lhost/x6.rb +0 -1
  48. data/lib/sisimai/lhost/zoho.rb +0 -12
  49. data/lib/sisimai/lhost.rb +38 -19
  50. data/lib/sisimai/message.rb +1 -1
  51. data/lib/sisimai/order.rb +4 -1
  52. data/lib/sisimai/reason/authfailure.rb +8 -7
  53. data/lib/sisimai/reason/badreputation.rb +5 -4
  54. data/lib/sisimai/reason/blocked.rb +6 -5
  55. data/lib/sisimai/reason/contenterror.rb +8 -5
  56. data/lib/sisimai/reason/delivered.rb +3 -2
  57. data/lib/sisimai/reason/emailtoolarge.rb +7 -6
  58. data/lib/sisimai/reason/expired.rb +16 -4
  59. data/lib/sisimai/reason/failedstarttls.rb +5 -3
  60. data/lib/sisimai/reason/feedback.rb +2 -1
  61. data/lib/sisimai/reason/filtered.rb +9 -8
  62. data/lib/sisimai/reason/hasmoved.rb +5 -4
  63. data/lib/sisimai/reason/hostunknown.rb +14 -4
  64. data/lib/sisimai/reason/mailboxfull.rb +10 -8
  65. data/lib/sisimai/reason/mailererror.rb +3 -2
  66. data/lib/sisimai/reason/networkerror.rb +16 -3
  67. data/lib/sisimai/reason/norelaying.rb +8 -6
  68. data/lib/sisimai/reason/notaccept.rb +15 -7
  69. data/lib/sisimai/reason/notcompliantrfc.rb +5 -3
  70. data/lib/sisimai/reason/onhold.rb +8 -7
  71. data/lib/sisimai/reason/policyviolation.rb +11 -5
  72. data/lib/sisimai/reason/ratelimited.rb +5 -4
  73. data/lib/sisimai/reason/rejected.rb +24 -14
  74. data/lib/sisimai/reason/requireptr.rb +5 -4
  75. data/lib/sisimai/reason/securityerror.rb +5 -3
  76. data/lib/sisimai/reason/spamdetected.rb +6 -5
  77. data/lib/sisimai/reason/suppressed.rb +4 -3
  78. data/lib/sisimai/reason/suspend.rb +8 -3
  79. data/lib/sisimai/reason/syntaxerror.rb +5 -11
  80. data/lib/sisimai/reason/systemerror.rb +21 -2
  81. data/lib/sisimai/reason/systemfull.rb +4 -3
  82. data/lib/sisimai/reason/undefined.rb +2 -1
  83. data/lib/sisimai/reason/userunknown.rb +15 -9
  84. data/lib/sisimai/reason/vacation.rb +2 -1
  85. data/lib/sisimai/reason/virusdetected.rb +6 -5
  86. data/lib/sisimai/reason.rb +47 -42
  87. data/lib/sisimai/rfc1123.rb +1 -1
  88. data/lib/sisimai/rfc1894.rb +15 -8
  89. data/lib/sisimai/rfc2045.rb +8 -5
  90. data/lib/sisimai/rfc3464/thirdparty.rb +11 -10
  91. data/lib/sisimai/rfc3464.rb +10 -14
  92. data/lib/sisimai/rfc3834.rb +6 -5
  93. data/lib/sisimai/rfc791.rb +3 -38
  94. data/lib/sisimai/rhost/aol.rb +3 -2
  95. data/lib/sisimai/rhost/apple.rb +12 -11
  96. data/lib/sisimai/rhost/cloudflare.rb +3 -2
  97. data/lib/sisimai/rhost/cox.rb +38 -37
  98. data/lib/sisimai/rhost/facebook.rb +17 -16
  99. data/lib/sisimai/rhost/franceptt.rb +39 -38
  100. data/lib/sisimai/rhost/godaddy.rb +35 -34
  101. data/lib/sisimai/rhost/google.rb +22 -21
  102. data/lib/sisimai/rhost/gsuite.rb +8 -7
  103. data/lib/sisimai/rhost/iua.rb +11 -10
  104. data/lib/sisimai/rhost/kddi.rb +3 -2
  105. data/lib/sisimai/rhost/messagelabs.rb +13 -12
  106. data/lib/sisimai/rhost/microsoft.rb +37 -32
  107. data/lib/sisimai/rhost/mimecast.rb +18 -17
  108. data/lib/sisimai/rhost/nttdocomo.rb +10 -9
  109. data/lib/sisimai/rhost/outlook.rb +3 -2
  110. data/lib/sisimai/rhost/spectrum.rb +19 -18
  111. data/lib/sisimai/rhost/tencent.rb +10 -9
  112. data/lib/sisimai/rhost/yahooinc.rb +11 -10
  113. data/lib/sisimai/rhost/zoho.rb +8 -7
  114. data/lib/sisimai/rhost.rb +1 -1
  115. data/lib/sisimai/smtp/command.rb +13 -8
  116. data/lib/sisimai/smtp/failure.rb +9 -8
  117. data/lib/sisimai/smtp/reply.rb +23 -22
  118. data/lib/sisimai/smtp/status.rb +164 -159
  119. data/lib/sisimai/smtp/transcript.rb +3 -2
  120. data/lib/sisimai/string.rb +0 -12
  121. data/lib/sisimai/version.rb +1 -1
  122. data/lib/sisimai.rb +2 -1
  123. data/set-of-emails/maildir/bsd/lhost-deutschetelekom-01.eml +66 -0
  124. data/set-of-emails/maildir/bsd/lhost-deutschetelekom-02.eml +68 -0
  125. data/set-of-emails/maildir/bsd/lhost-deutschetelekom-03.eml +50 -0
  126. data/set-of-emails/maildir/bsd/lhost-postfix-81.eml +130 -0
  127. data/set-of-emails/should-not-crash/go-423-deeply-nested-mime-parts.eml +20050 -0
  128. data/set-of-emails/should-not-crash/p5-664-iomart-mail-filter.eml +258 -0
  129. data/set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yet/.gitkeep +0 -0
  130. metadata +11 -2
@@ -1,10 +1,10 @@
1
1
  module Sisimai
2
2
  module Reason
3
- # Sisimai::Reason::VirusDetected checks the bounce reason is "virusdetected" or not. This class
3
+ # Sisimai::Reason::VirusDetected checks the bounce reason is "VirusDetected" or not. This class
4
4
  # is called only Sisimai::Reason class.
5
5
  #
6
6
  # This is an error that any virus or trojan horse detected in the message by a virus scanner program
7
- # at a destination mail server. This reason has been divided from "securityerror" at Sisimai 4.22.0.
7
+ # at a destination mail server. This reason has been divided from "SecurityError" at Sisimai 4.22.0.
8
8
  #
9
9
  # Your message was infected with a virus. You should download a virus
10
10
  # scanner and check your computer for viruses.
@@ -14,13 +14,14 @@ module Sisimai
14
14
  #
15
15
  module VirusDetected
16
16
  class << self
17
+ require 'sisimai/eb'
17
18
  Index = ["it has a potentially executable attachment"].freeze
18
19
  Pairs = [
19
20
  ["message was ", "ected", " virus"],
20
21
  ["virus", " detected"],
21
22
  ].freeze
22
23
 
23
- def text; return 'virusdetected'; end
24
+ def text; return Sisimai::Eb::ReEXEC; end
24
25
  def description; return 'Email rejected due to a virus scanner on a destination host'; end
25
26
 
26
27
  # Try to match that the given text and regular expressions
@@ -34,7 +35,7 @@ module Sisimai
34
35
  return false
35
36
  end
36
37
 
37
- # The bounce reason is "virusdetected" or not
38
+ # The bounce reason is "VirusDetected" or not
38
39
  # @param [Sisimai::Fact] argvs Object to be detected the reason
39
40
  # @return [Boolean] true: virus detected
40
41
  # false: virus was not detected
@@ -44,7 +45,7 @@ module Sisimai
44
45
  # The value of "reason" isn't "visusdetected" when the value of "command" is an SMTP command
45
46
  # to be sent before the SMTP DATA command because all the MTAs read the headers and the
46
47
  # entire message body after the DATA command.
47
- return true if argvs['reason'] == 'virusdetected'
48
+ return true if argvs['reason'] == Sisimai::Eb::ReEXEC
48
49
  return false if Sisimai::SMTP::Command::ExceptDATA.include?(argvs['command'])
49
50
  return match(argvs['diagnosticcode'].downcase)
50
51
  end
@@ -3,24 +3,28 @@ module Sisimai
3
3
  # Sisimai::Fact object as an argument of find() method. This class is called only Sisimai::Fact.
4
4
  module Reason
5
5
  class << self
6
+ require 'sisimai/eb'
7
+
6
8
  # All the error reason list Sisimai support
7
9
  # @return [Array] Reason list
8
10
  def index
9
- return %w[
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
11
+ return [
12
+ Sisimai::Eb::ReAUTH, Sisimai::Eb::ReFAMA, Sisimai::Eb::ReBLOC, Sisimai::Eb::ReBODY, Sisimai::Eb::ReSIZE,
13
+ Sisimai::Eb::ReTIME, Sisimai::Eb::ReTTLS, Sisimai::Eb::ReFILT, Sisimai::Eb::ReMOVE, Sisimai::Eb::ReHOST,
14
+ Sisimai::Eb::ReFULL, Sisimai::Eb::ReUNIX, Sisimai::Eb::ReINET, Sisimai::Eb::Re00MX, Sisimai::Eb::ReNRFC,
15
+ Sisimai::Eb::ReRATE, Sisimai::Eb::Re___1, Sisimai::Eb::ReFROM, Sisimai::Eb::RePASS, Sisimai::Eb::ReSPAM,
16
+ Sisimai::Eb::ReEXEC, Sisimai::Eb::ReWONT, Sisimai::Eb::ReSAFE, Sisimai::Eb::ReQUIT, Sisimai::Eb::ReQPTR,
17
+ Sisimai::Eb::RePROC, Sisimai::Eb::ReDISK, Sisimai::Eb::ReSTOP, Sisimai::Eb::ReUSER, Sisimai::Eb::ReCOMM,
14
18
  ]
15
19
  end
16
20
 
17
- # @abstract is_explicit() returns 0 when the argument is empty or is "undefined" or is "onhold"
21
+ # @abstract is_explicit() returns 0 when the argument is empty or is "Undefined" or is "OnHold"
18
22
  # @param string argv1 Reason name
19
23
  # @return bool false: The reaosn is not explicit
20
24
  def is_explicit(argv1 = '')
21
25
  return false if argv1.nil?
22
26
  return false if argv1.empty?
23
- return false if argv1 == "undefined" || argv1 == "onhold" || argv1.empty?
27
+ return false if argv1 == Sisimai::Eb::Re___0 || argv1 == Sisimai::Eb::Re___1 || argv1.empty?
24
28
  return true
25
29
  end
26
30
 
@@ -38,29 +42,30 @@ module Sisimai
38
42
  # @return [Hash] Reason list
39
43
  def retry
40
44
  return {
41
- 'undefined' => true, 'onhold' => true, 'systemerror' => true, 'securityerror' => true,
42
- 'expired' => true, 'networkerror' => true, 'hostunknown' => true, 'userunknown' => true
45
+ Sisimai::Eb::Re___0 => true, Sisimai::Eb::Re___1 => true, Sisimai::Eb::RePROC => true,
46
+ Sisimai::Eb::ReSAFE => true, Sisimai::Eb::ReTIME => true, Sisimai::Eb::ReINET => true,
47
+ Sisimai::Eb::ReHOST => true, Sisimai::Eb::ReUSER => true,
43
48
  }.freeze
44
49
  end
45
50
  ModulePath = Sisimai::Reason.path
46
51
  GetRetried = Sisimai::Reason.retry
47
52
  ClassOrder = [
48
- %w[
49
- MailboxFull EmailTooLarge Suspend HasMoved NoRelaying AuthFailure UserUnknown Filtered
50
- RequirePTR NotCompliantRFC BadReputation ContentError Rejected HostUnknown SpamDetected
51
- RateLimited Blocked
52
- ],
53
- %w[
54
- MailboxFull AuthFailure BadReputation SpamDetected VirusDetected PolicyViolation NoRelaying
55
- SystemError NetworkError Suspend ContentError SystemFull NotAccept Expired FailedSTARTTLS
56
- SecurityError Suppressed MailerError
57
- ],
58
- %w[
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
- ]
53
+ # 0. true() meethod in the following reasons are called from Reason->find()
54
+ [Sisimai::Eb::ReFULL, Sisimai::Eb::ReSIZE, Sisimai::Eb::ReQUIT, Sisimai::Eb::ReMOVE, Sisimai::Eb::RePASS,
55
+ Sisimai::Eb::ReAUTH, Sisimai::Eb::ReUSER, Sisimai::Eb::ReFILT, Sisimai::Eb::ReQPTR, Sisimai::Eb::ReNRFC,
56
+ Sisimai::Eb::ReFAMA, Sisimai::Eb::ReBODY, Sisimai::Eb::ReFROM, Sisimai::Eb::ReHOST, Sisimai::Eb::ReSPAM,
57
+ Sisimai::Eb::ReRATE, Sisimai::Eb::ReBLOC, Sisimai::Eb::ReTTLS, Sisimai::Eb::Re00MX, Sisimai::Eb::ReEXEC,
58
+ Sisimai::Eb::ReWONT],
59
+
60
+ # 1. match() method in the following reasons are called from Reason->find()
61
+ [Sisimai::Eb::ReFULL, Sisimai::Eb::ReSPAM, Sisimai::Eb::ReEXEC, Sisimai::Eb::RePASS, Sisimai::Eb::RePROC,
62
+ Sisimai::Eb::ReINET, Sisimai::Eb::ReQUIT, Sisimai::Eb::ReDISK, Sisimai::Eb::ReSTOP, Sisimai::Eb::ReUNIX,
63
+ Sisimai::Eb::ReSAFE, Sisimai::Eb::ReWONT, Sisimai::Eb::ReCOMM, Sisimai::Eb::ReTIME],
64
+ [Sisimai::Eb::ReFULL, Sisimai::Eb::ReSIZE, Sisimai::Eb::ReQUIT, Sisimai::Eb::ReUSER, Sisimai::Eb::ReFILT,
65
+ Sisimai::Eb::ReFROM, Sisimai::Eb::ReHOST, Sisimai::Eb::ReSPAM, Sisimai::Eb::ReRATE, Sisimai::Eb::ReBLOC,
66
+ Sisimai::Eb::ReAUTH, Sisimai::Eb::ReTTLS, Sisimai::Eb::ReSAFE, Sisimai::Eb::RePROC, Sisimai::Eb::ReINET,
67
+ Sisimai::Eb::ReTIME, Sisimai::Eb::ReBODY, Sisimai::Eb::ReMOVE, Sisimai::Eb::ReDISK, Sisimai::Eb::Re00MX,
68
+ Sisimai::Eb::ReUNIX, Sisimai::Eb::RePASS, Sisimai::Eb::ReSTOP, Sisimai::Eb::ReCOMM, Sisimai::Eb::Re___1],
64
69
  ]
65
70
 
66
71
  # Detect the bounce reason
@@ -74,7 +79,7 @@ module Sisimai
74
79
  # retry() method.
75
80
  return argvs['reason'] if argvs['reason'].empty? == false
76
81
  end
77
- return 'delivered' if argvs['deliverystatus'].start_with?('2.')
82
+ return Sisimai::Eb::ReSENT if argvs['deliverystatus'].start_with?('2.')
78
83
 
79
84
  reasontext = ''
80
85
  issuedcode = argvs['diagnosticcode'] || ''
@@ -100,21 +105,21 @@ module Sisimai
100
105
  end
101
106
  end
102
107
 
103
- if reasontext.empty? || reasontext == 'undefined'
108
+ if reasontext.empty? || reasontext == Sisimai::Eb::Re___0
104
109
  # Bounce reason is not detected yet.
105
110
  reasontext = self.anotherone(argvs)
106
111
 
107
- if reasontext == 'undefined' || reasontext.empty?
112
+ if reasontext == Sisimai::Eb::Re___0 || reasontext.empty?
108
113
  # Action: delayed => "expired"
109
114
  reasontext = nil
110
- reasontext ||= 'expired' if argvs['action'] == 'delayed'
115
+ reasontext ||= Sisimai::Eb::ReTIME if argvs['action'] == Sisimai::Eb::AeSTAY
111
116
  return reasontext if reasontext
112
117
 
113
118
  # Try to match with message patterns in Sisimai::Reason::Vacation
114
119
  require 'sisimai/reason/vacation'
115
- reasontext = 'vacation' if Sisimai::Reason::Vacation.match(issuedcode.downcase)
116
- reasontext ||= 'onhold' if issuedcode.empty? == false
117
- reasontext ||= 'undefined'
120
+ reasontext = Sisimai::Eb::ReAWAY if Sisimai::Reason::Vacation.match(issuedcode.downcase)
121
+ reasontext ||= Sisimai::Eb::Re___1 if issuedcode.empty? == false
122
+ reasontext ||= Sisimai::Eb::Re___0
118
123
  end
119
124
  end
120
125
  return reasontext
@@ -151,7 +156,7 @@ module Sisimai
151
156
  end
152
157
 
153
158
  next if r.match(issuedcode) == false
154
- reasontext = e.downcase
159
+ reasontext = e
155
160
  break
156
161
  end
157
162
  break if reasontext.empty? == false
@@ -160,31 +165,31 @@ module Sisimai
160
165
  code2digit = statuscode[0, 3] || ''
161
166
  if code2digit == '5.6' || code2digit == '4.6'
162
167
  # X.6.0 Other or undefined media error
163
- reasontext = 'contenterror'
168
+ reasontext = Sisimai::Eb::ReBODY
164
169
 
165
170
  elsif code2digit == '5.7' || code2digit == '4.7'
166
171
  # X.7.0 Other or undefined security status
167
- reasontext = 'securityerror'
172
+ reasontext = Sisimai::Eb::ReSAFE
168
173
 
169
174
  elsif codeformat.start_with?('X-UNIX')
170
175
  # Diagnostic-Code: X-UNIX; ...
171
- reasontext = 'mailererror'
176
+ reasontext = Sisimai::Eb::ReUNIX
172
177
 
173
178
  else
174
179
  # 50X Syntax Error?
175
180
  require 'sisimai/reason/syntaxerror'
176
- reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
181
+ reasontext = Sisimai::Eb::ReCOMM if Sisimai::Reason::SyntaxError.true(argvs)
177
182
  end
178
183
  break if reasontext.empty? == false
179
184
 
180
185
  # Check the value of Action: field, first
181
- if actiontext.start_with?('delayed', 'expired')
186
+ if actiontext.start_with?(Sisimai::Eb::AeSTAY, 'expired')
182
187
  # Action: delayed, expired
183
- reasontext = 'expired'
188
+ reasontext = Sisimai::Eb::ReTIME
184
189
  else
185
190
  # Rejected at connection or after EHLO|HELO
186
191
  thecommand = argvs['command'] || ''
187
- reasontext = 'blocked' if %w[HELO EHLO].index(thecommand)
192
+ reasontext = Sisimai::Eb::ReBLOC if %w[HELO EHLO].index(thecommand)
188
193
  end
189
194
  break
190
195
  end
@@ -222,12 +227,12 @@ module Sisimai
222
227
 
223
228
  if issuedcode.upcase.include?('X-UNIX; ')
224
229
  # X-Unix; ...
225
- reasontext = 'mailererror'
230
+ reasontext = Sisimai::Eb::ReUNIX
226
231
  else
227
232
  # Detect the bounce reason from "Status:" code
228
233
  require 'sisimai/smtp/status'
229
234
  reasontext = Sisimai::SMTP::Status.name(Sisimai::SMTP::Status.find(argv1))
230
- reasontext = "undefined" if reasontext.empty?
235
+ reasontext = Sisimai::Eb::Re___0 if reasontext.empty?
231
236
  end
232
237
  return reasontext
233
238
  end
@@ -125,7 +125,7 @@ module Sisimai
125
125
  sourcetext = sourcetext.gsub("[", " [").gsub("(", " (").gsub("<", " <") # Prefix a space character before each bracket
126
126
  sourcetext = sourcetext.gsub("]", "] ").gsub(")", ") ").gsub(">", "> ") # Suffix a space character behind each bracket
127
127
  sourcetext = sourcetext.gsub(":", ": ").gsub(";", "; ") # Suffix a space character behind : and ;
128
- sourcetext = Sisimai::String.sweep(sourcetext)
128
+ sourcetext = sourcetext.split.join(" ")
129
129
 
130
130
  catch :MAKELIST do
131
131
  Sandwiched.each do |e|
@@ -2,6 +2,7 @@ module Sisimai
2
2
  # Sisimai::RFC1894 DSN field defined in RFC3464 (obsoletes RFC1894)
3
3
  module RFC1894
4
4
  class << self
5
+ require 'sisimai/eb'
5
6
  require 'sisimai/string'
6
7
  FieldNames = [
7
8
  # https://tools.ietf.org/html/rfc3464#section-2.2
@@ -58,8 +59,13 @@ module Sisimai
58
59
  }.freeze
59
60
 
60
61
  SubtypeSet = {"addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS"}.freeze
61
- ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
62
- Correction = {'deliverable' => 'delivered', 'expired' => 'failed', 'failure' => 'failed'}
62
+ ActionList = [Sisimai::Eb::AeFAIL, Sisimai::Eb::AeSTAY, Sisimai::Eb::AeSENT,
63
+ Sisimai::Eb::AePASS, Sisimai::Eb::AeEXPN].freeze
64
+ Correction = {
65
+ 'deliverable' => Sisimai::Eb::AeSENT,
66
+ 'expired' => Sisimai::Eb::AeFAIL,
67
+ 'failure' => Sisimai::Eb::AeFAIL,
68
+ }
63
69
  FieldGroup = {
64
70
  'original-recipient' => 'addr',
65
71
  'final-recipient' => 'addr',
@@ -146,7 +152,7 @@ module Sisimai
146
152
  return nil if argv0.empty?
147
153
  label = Sisimai::RFC1894.label(argv0)
148
154
  group = FieldGroup[label] || ''
149
- parts = argv0.split(":", 2); parts[1] = parts[1].nil? ? "" : Sisimai::String.sweep(parts[1])
155
+ parts = argv0.split(":", 2); parts[1] = parts[1].nil? ? "" : parts[1].split.join(" ")
150
156
  return nil if group.empty? || CapturesOn[group].nil?
151
157
 
152
158
  # Try to match with each pattern of Per-Message field, Per-Recipient field
@@ -157,24 +163,25 @@ module Sisimai
157
163
  # - 4: Comment
158
164
  table = [label, "", "", group, ""]
159
165
 
160
- if group == 'addr' || group == 'code' || group == 'host'
166
+ case group
167
+ when "addr", "code", "host"
161
168
  # - Final-Recipient: RFC822; kijitora@nyaan.jp
162
169
  # - Diagnostic-Code: SMTP; 550 5.1.1 <kijitora@example.jp>... User Unknown
163
170
  # - Remote-MTA: DNS; mx.example.jp
164
171
  if parts[1].include?(";")
165
172
  # There is a valid sub type (including ";")
166
173
  v = parts[1].split(";", 2)
167
- table[1] = Sisimai::String.sweep(v[0]).upcase if v.size > 0
168
- table[2] = Sisimai::String.sweep(v[1]) if v.size > 1
174
+ table[1] = v[0].split.join(" ").upcase if v.size > 0
175
+ table[2] = v[1].split.join(" ") if v.size > 1
169
176
  else
170
177
  # There is no sub type like "Diagnostic-Code: 550 5.1.1 <kijitora@example.jp>..."
171
- table[2] = Sisimai::String.sweep(parts[1])
178
+ table[2] = parts[1]
172
179
  table[1] = SubtypeSet[group] || ""
173
180
  end
174
181
  table[2] = table[2].downcase if group == "host"
175
182
  table[2] = "" if table[2] =~ /\A\s+\z/
176
183
 
177
- elsif group == "list"
184
+ when "list"
178
185
  # Action: failed
179
186
  # Check that the value is an available value defined in "ActionList" or not.
180
187
  # When the value is invalid, convert to an available value defined in "Correction"
@@ -146,7 +146,7 @@ module Sisimai
146
146
  return nil if block.empty?
147
147
 
148
148
  (upperchunk, lowerchunk) = block.split("\n\n", 2)
149
- return ['', ''] if upperchunk.nil? || upperchunk.empty?
149
+ return ['', ''] if upperchunk.nil? || upperchunk.empty? || lowerchunk.nil?
150
150
  return ['', ''] if upperchunk.index('Content-Type').nil?
151
151
 
152
152
  headerpart = ['', ''] # ["text/plain; charset=iso-2022-jp; ...", "quoted-printable"]
@@ -213,14 +213,17 @@ module Sisimai
213
213
  # Split argv1: multipart/* blocks by a boundary string in argv0
214
214
  # @param [String] argv0 The value of Content-Type header
215
215
  # @param [String] argv1 A pointer to multipart/* message blocks
216
+ # @param [String] depth Depth of MIME parts
216
217
  # @return [Array] List of each part of multipart/*
217
218
  # @since v5.0.0
218
- def levelout(argv0 = '', argv1 = '')
219
+ def levelout(argv0 = '', argv1 = '', depth = 0)
219
220
  return [] if argv0.empty?
220
221
  return [] if argv1.empty?
222
+ return [] if depth > 100
221
223
 
222
224
  boundary01 = boundary(argv0, 0); return [] if boundary01.empty?
223
225
  multiparts = argv1.split(Regexp.new(Regexp.escape(boundary01) + "\n"))
226
+ partsdepth = depth + 1
224
227
  partstable = []
225
228
 
226
229
  # Remove empty or useless preamble and epilogue of multipart/* block
@@ -237,7 +240,7 @@ module Sisimai
237
240
  bodyinside = f[-1].split("\n\n", 2)[-1]
238
241
  next if bodyinside.size < 9 || bodyinside.index(boundary02).nil?
239
242
 
240
- v = levelout(f[0], bodyinside)
243
+ v = levelout(f[0], bodyinside, partsdepth)
241
244
  partstable += v if v.size > 0
242
245
  else
243
246
  # The part is not a multipart/* block
@@ -263,7 +266,7 @@ module Sisimai
263
266
  # @return [String] Message body
264
267
  def makeflat(argv0 = '', argv1 = '')
265
268
  return "" if argv0.nil? || argv1.nil?
266
- return "" if argv0.index('multipart/') == false || argv0.index('boundary=') == false
269
+ return "" if argv0.downcase.index('multipart/') == false || argv0.downcase.index('boundary=') == false
267
270
 
268
271
  # Some bounce messages include lower-cased "content-type:" field such as the followings:
269
272
  # - content-type: message/delivery-status => Content-Type: message/delivery-status
@@ -271,7 +274,7 @@ module Sisimai
271
274
  # - CHARSET=, BOUNDARY= => charset-, boundary=
272
275
  # - message/xdelivery-status => message/delivery-status
273
276
  iso2022set = %r/charset=["']?(iso-2022-[-a-z0-9]+)['"]?\b/
274
- multiparts = levelout(argv0, argv1)
277
+ multiparts = levelout(argv0, argv1, 0)
275
278
  flattenout = ''
276
279
  delimiters = ["/delivery-status", "/rfc822", "/feedback-report", "/partial"]
277
280
 
@@ -2,6 +2,7 @@ module Sisimai
2
2
  module RFC3464
3
3
  module ThirdParty
4
4
  class << self
5
+ require 'sisimai/eb'
5
6
  ThirdParty = {
6
7
  #"Aol" => ["X-Outbound-Mail-Relay-"], # X-Outbound-Mail-Relay-(Queue-ID|Sender)
7
8
  "PowerMTA" => ["X-PowerMTA-"], # X-PowerMTA-(VirtualMTA|BounceCategory)
@@ -50,15 +51,15 @@ module Sisimai
50
51
  "x-powermta-bouncecategory" => "text", # X-PowerMTA-BounceCategory: bad-mailbox
51
52
  }.freeze
52
53
  MessagesOf = {
53
- "bad-domain" => "hostunknown",
54
- "bad-mailbox" => "userunknown",
55
- "inactive-mailbox" => "suspend",
56
- "message-expired" => "expired",
57
- "no-answer-from-host" => "networkerror",
58
- "policy-related" => "policyviolation",
59
- "quota-issues" => "mailboxfull",
60
- "routing-errors" => "systemerror",
61
- "spam-related" => "spamdetected",
54
+ "bad-domain" => Sisimai::Eb::ReHOST,
55
+ "bad-mailbox" => Sisimai::Eb::ReUSER,
56
+ "inactive-mailbox" => Sisimai::Eb::ReQUIT,
57
+ "message-expired" => Sisimai::Eb::ReTIME,
58
+ "no-answer-from-host" => Sisimai::Eb::ReINET,
59
+ "policy-related" => Sisimai::Eb::ReWONT,
60
+ "quota-issues" => Sisimai::Eb::ReFULL,
61
+ "routing-errors" => Sisimai::Eb::RePROC,
62
+ "spam-related" => Sisimai::Eb::ReSPAM,
62
63
  }.freeze
63
64
 
64
65
  # Returns an array which is compatible with the value returned from Sisimai::RFC1894->field()
@@ -71,7 +72,7 @@ module Sisimai
71
72
  fieldparts = argv1.split(":", 2)
72
73
  xfieldname = fieldparts[0].downcase
73
74
  xgroupname = FieldGroup[xfieldname]; return [] if xgroupname.nil?
74
- xfieldlist = ["", "", Sisimai::String.sweep(fieldparts[1]), xgroupname, "", "PowerMTA"]
75
+ xfieldlist = ["", "", fieldparts[1].split.join(" "), xgroupname, "", "PowerMTA"]
75
76
 
76
77
  # - 0: Field-Name
77
78
  # - 1: Sub Type: RFC822, DNS, X-Unix, and so on)
@@ -109,16 +109,12 @@ module Sisimai
109
109
  if isboundary.any? { |a| e == a } then goestonext = false; break; end
110
110
  if e.start_with?("Content-Type:")
111
111
  # Content-Type: field in multipart/*
112
- if e.include?("multipart/")
112
+ case
113
113
  # Content-Type: multipart/alternative; boundary=aa00220022222222ffeebb
114
114
  # Pick the boundary string and store it into "isboucdary"
115
- isboundary << Sisimai::RFC2045.boundary(e, 0)
116
- elsif e.include?("text/plain")
117
- # Content-Type: "text/plain"
118
- goestonext = false
119
- else
120
- # Other types: for example, text/html, image/jpg, and so on
121
- goestonext = true
115
+ when e.include?("multipart/") then isboundary << Sisimai::RFC2045.boundary(e, 0)
116
+ when e.include?("text/plain") then goestonext = false
117
+ else goestonext = true # Other types: for example, text/html, image/jpg, and so on
122
118
  end
123
119
  break
124
120
  end
@@ -145,7 +141,8 @@ module Sisimai
145
141
  next unless o = Sisimai::RFC1894.field(e)
146
142
  v = dscontents[-1]
147
143
 
148
- if o[3] == "addr"
144
+ case o[3]
145
+ when "addr"
149
146
  # Final-Recipient: rfc822; kijitora@example.jp
150
147
  # X-Actual-Recipient: rfc822; kijitora@example.co.jp
151
148
  if o[0] == "final-recipient"
@@ -165,7 +162,7 @@ module Sisimai
165
162
  # X-Actual-Recipient: rfc822; kijitora@example.co.jp
166
163
  v["alias"] = o[2]
167
164
  end
168
- elsif o[3] == "code"
165
+ when "code"
169
166
  # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
170
167
  v["spec"] = o[1]
171
168
  v["diagnosis"] = o[2]
@@ -212,7 +209,7 @@ module Sisimai
212
209
  # Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp]
213
210
  # has an SPF record with a hard fail
214
211
  next if e.start_with?(" ") == false
215
- v["diagnosis"] += " #{Sisimai::String.sweep(e)}"
212
+ v["diagnosis"] += " " + e
216
213
  end
217
214
  end
218
215
  end
@@ -232,7 +229,7 @@ module Sisimai
232
229
 
233
230
  if beforemesg != ""
234
231
  # Pick some values of $dscontents from the string before StartingOf[:message]
235
- beforemesg = Sisimai::String.sweep(beforemesg)
232
+ beforemesg = beforemesg.split.join(" ")
236
233
  alternates["command"] = Sisimai::SMTP::Command.find(beforemesg)
237
234
  alternates["replycode"] = Sisimai::SMTP::Reply.find(beforemesg, dscontents[0]["status"])
238
235
  alternates["status"] = Sisimai::SMTP::Status.find(beforemesg, alternates["replycode"])
@@ -242,7 +239,6 @@ module Sisimai
242
239
  dscontents.each do |e|
243
240
  # Set default values stored in "permessage" if each value in "dscontents" is empty.
244
241
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
245
- e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"])
246
242
  lowercased = e["diagnosis"].downcase
247
243
 
248
244
  if recipients == 1
@@ -254,7 +250,7 @@ module Sisimai
254
250
  else
255
251
  # The value of e["diagnosis"] is not contained in $beforemesg
256
252
  # There may be an important error message in $beforemesg
257
- e["diagnosis"] = Sisimai::String.sweep(sprintf("%s %s", beforemesg, e["diagnosis"]))
253
+ e["diagnosis"] = sprintf("%s %s", beforemesg, e["diagnosis"])
258
254
  end
259
255
  end
260
256
  e["command"] = Sisimai::SMTP::Command.find(e["diagnosis"])
@@ -3,6 +3,8 @@ module Sisimai
3
3
  module RFC3834
4
4
  class << self
5
5
  # http://tools.ietf.org/html/rfc3834
6
+ require 'sisimai/eb'
7
+
6
8
  MarkingsOf = {:boundary => '__SISIMAI_PSEUDO_BOUNDARY__'}
7
9
  LowerLabel = %w[from to subject auto-submitted precedence x-apple-action].freeze
8
10
  DoNotParse = {
@@ -116,19 +118,18 @@ module Sisimai
116
118
  break if haveloaded >= maxmsgline
117
119
  end
118
120
  v['diagnosis'] ||= mhead['subject']
119
- v['diagnosis'] = Sisimai::String.sweep(v['diagnosis'])
120
- v['reason'] = 'vacation'
121
+ v['reason'] = Sisimai::Eb::ReAWAY
121
122
 
122
123
  cv = v['diagnosis'].downcase
123
124
  Suspending.each do |e|
124
125
  # Check that the auto-replied message indicates the "Suspend" reason or not.
125
126
  next unless Sisimai::String.aligned(cv, e)
126
- v['reason'] = 'suspend'
127
+ v['reason'] = Sisimai::Eb::ReQUIT
127
128
  break
128
129
  end
129
130
 
130
- v['date'] = mhead['date']
131
- v['status'] = ''
131
+ v['date'] = mhead['date']
132
+ v['status'] = ''
132
133
 
133
134
  if cv = lower['subject'].match(SubjectSet)
134
135
  # Get the Subject header from the original message
@@ -12,8 +12,7 @@ module Sisimai
12
12
 
13
13
  octet.each do |e|
14
14
  # Check each octet is between 0 and 255
15
- return false unless e =~ /\A[0-9]{1,3}\z/
16
- v = e.to_i
15
+ return false unless e =~ /\A[0-9]{1,3}\z/; v = e.to_i
17
16
  return false if v < 0 || v > 255
18
17
  end
19
18
  return true
@@ -27,42 +26,8 @@ module Sisimai
27
26
  return nil if argv0.to_s.empty?
28
27
  return [] if argv0.size < 7
29
28
 
30
- given = argv0.dup
31
- ipv4a = []
32
- %w|( ) [ ] ,|.each do |e|
33
- # Rewrite: "mx.example.jp[192.0.2.1]" => "mx.example.jp 192.0.2.1"
34
- p0 = given.index(e); next if p0.nil?
35
- given[p0, 1] = ' '
36
- end
37
-
38
- given.split(' ').each do |e|
39
- # Find string including an IPv4 address
40
- next if e.index('.').nil? # IPv4 address must include "." character
41
-
42
- lx = e.size; next if lx < 7 || lx > 17 # 0.0.0.0 = 7, [255.255.255.255] = 17
43
- cu = 0 # Cursor for seeking each octet of an IPv4 address
44
- as = '' # ASCII Code of each character
45
- eo = '' # Buffer of each octet of IPv4 Address
46
-
47
- while cu < lx
48
- # Check whether each character is a number or "." or not
49
- as = e[cu, 1].ord
50
- cu += 1
51
-
52
- if as < 48 || as > 57
53
- # The character is not a number(0-9)
54
- break if as != 46 # The character is not "."
55
- next if eo == '' # The current buffer is empty
56
- break if eo.to_i > 255 # The current buffer is greater than 255
57
- eo = ''
58
- next
59
- end
60
- eo += as.chr
61
- break if eo.to_i > 255
62
- end
63
- ipv4a << e if eo.size > 0 && eo.to_i < 256
64
- end
65
- return ipv4a
29
+ given = argv0.dup.tr('()[],', ' ').squeeze(' ')
30
+ return given.split(' ').select { |e| is_ipv4address(e) }
66
31
  end
67
32
 
68
33
  end
@@ -5,9 +5,10 @@ module Sisimai
5
5
  # only from Sisimai::Fact class.
6
6
  module Aol
7
7
  class << self
8
+ require 'sisimai/eb'
8
9
  MessagesOf = {
9
- "hostunknown" => ["Host or domain name not found"],
10
- "notaccept" => ["type=MX: Malformed or unexpected name server reply"],
10
+ Sisimai::Eb::ReHOST => ["Host or domain name not found"],
11
+ Sisimai::Eb::Re00MX => ["type=MX: Malformed or unexpected name server reply"],
11
12
  }.freeze
12
13
 
13
14
  # Detect bounce reason from Aol Mail: https://www.aol.com
@@ -5,8 +5,9 @@ module Sisimai
5
5
  # This class is called only Sisimai::Fact class.
6
6
  module Apple
7
7
  class << self
8
+ require 'sisimai/eb'
8
9
  MessagesOf = {
9
- 'authfailure' => [
10
+ Sisimai::Eb::ReAUTH => [
10
11
  # - 554 5.7.1 Your message was rejected due to example.jp's DMARC policy.
11
12
  # See https://support.apple.com/en-us/HT204137 for
12
13
  # - 554 5.7.1 [HME1] This message was blocked for failing both SPF and DKIM authentication
@@ -14,7 +15,7 @@ module Sisimai
14
15
  's dmarc policy',
15
16
  'blocked for failing both spf and dkim autentication checks',
16
17
  ],
17
- 'blocked' => [
18
+ Sisimai::Eb::ReBLOC => [
18
19
  # - 550 5.7.0 Blocked - see https://support.proofpoint.com/dnsbl-lookup.cgi?ip=192.0.1.2
19
20
  # - 550 5.7.1 Your email was rejected due to having a domain present in the Spamhaus
20
21
  # DBL -- see https://www.spamhaus.org/dbl/
@@ -26,39 +27,39 @@ module Sisimai
26
27
  'blocked - see https://support.proofpoint.com/dnsbl-lookup',
27
28
  'not accepting connections',
28
29
  ],
29
- 'hasmoved' => [
30
+ Sisimai::Eb::ReMOVE => [
30
31
  # - 550 5.1.6 recipient no longer on server: *****@icloud.com
31
32
  'recipient no longer on server',
32
33
  ],
33
- 'mailboxfull' => [
34
+ Sisimai::Eb::ReFULL => [
34
35
  # - 552 5.2.2 <****@icloud.com>: user is over quota (in reply to RCPT TO command)
35
36
  'user is over quota',
36
37
  ],
37
- 'norelaying' => [
38
+ Sisimai::Eb::RePASS => [
38
39
  # - 554 5.7.1 <*****@icloud.com>: Relay access denied
39
40
  'relay access denied',
40
41
  ],
41
- 'notaccept' => ['host/domain does not accept mail'],
42
- 'policyviolation' => [
42
+ Sisimai::Eb::Re00MX => ['host/domain does not accept mail'],
43
+ Sisimai::Eb::ReWONT => [
43
44
  # - 550 5.7.1 [CS01] Message rejected due to local policy.
44
45
  # Please visit https://support.apple.com/en-us/HT204137
45
46
  'due to local policy',
46
47
  ],
47
- 'ratelimited' => [
48
+ Sisimai::Eb::ReRATE => [
48
49
  # - 421 4.7.1 Messages to ****@icloud.com deferred due to excessive volume.
49
50
  # Try again later - https://support.apple.com/en-us/HT204137
50
51
  'due to excessive volume',
51
52
  ],
52
- 'rejected' => [
53
+ Sisimai::Eb::ReFROM => [
53
54
  # - 450 4.1.8 <kijitora@example.jp>: Sender address rejected: Domain not found
54
55
  'sender address rejected',
55
56
  ],
56
- 'suspend' => [
57
+ Sisimai::Eb::ReQUIT => [
57
58
  # - https://support.apple.com/guide/icloud/stop-using-or-reactivate-addresses-mm3adb030cbf/icloud
58
59
  # - 550 5.1.1 <****@icloud.com>: inactive email address (in reply to RCPT TO command)
59
60
  "inactive email address",
60
61
  ],
61
- 'userunknown' => [
62
+ Sisimai::Eb::ReUSER => [
62
63
  # - 550 5.1.1 <****@icloud.com>: inactive email address (in reply to RCPT TO command)
63
64
  # - 550 5.1.1 unknown or illegal alias: ****@icloud.com
64
65
  'user does not exist',