sisimai 5.1.0-java → 5.2.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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +1 -1
  3. data/ChangeLog.md +102 -0
  4. data/Makefile +4 -2
  5. data/README-JA.md +23 -16
  6. data/README.md +22 -15
  7. data/lib/sisimai/arf.rb +121 -210
  8. data/lib/sisimai/fact.rb +208 -158
  9. data/lib/sisimai/lda.rb +98 -0
  10. data/lib/sisimai/lhost/activehunter.rb +1 -1
  11. data/lib/sisimai/lhost/amazonses.rb +185 -301
  12. data/lib/sisimai/lhost/apachejames.rb +48 -51
  13. data/lib/sisimai/lhost/biglobe.rb +1 -2
  14. data/lib/sisimai/lhost/courier.rb +10 -8
  15. data/lib/sisimai/lhost/domino.rb +25 -25
  16. data/lib/sisimai/lhost/dragonfly.rb +3 -4
  17. data/lib/sisimai/lhost/einsundeins.rb +3 -4
  18. data/lib/sisimai/lhost/exchange2003.rb +6 -8
  19. data/lib/sisimai/lhost/exchange2007.rb +111 -101
  20. data/lib/sisimai/lhost/exim.rb +232 -242
  21. data/lib/sisimai/lhost/ezweb.rb +43 -51
  22. data/lib/sisimai/lhost/fml.rb +2 -3
  23. data/lib/sisimai/lhost/gmail.rb +32 -28
  24. data/lib/sisimai/lhost/gmx.rb +4 -16
  25. data/lib/sisimai/lhost/googlegroups.rb +9 -8
  26. data/lib/sisimai/lhost/googleworkspace.rb +94 -0
  27. data/lib/sisimai/lhost/imailserver.rb +7 -16
  28. data/lib/sisimai/lhost/interscanmss.rb +1 -1
  29. data/lib/sisimai/lhost/kddi.rb +3 -4
  30. data/lib/sisimai/lhost/mailfoundry.rb +2 -5
  31. data/lib/sisimai/lhost/mailmarshalsmtp.rb +1 -2
  32. data/lib/sisimai/lhost/messagingserver.rb +14 -13
  33. data/lib/sisimai/lhost/mfilter.rb +4 -3
  34. data/lib/sisimai/lhost/notes.rb +2 -4
  35. data/lib/sisimai/lhost/opensmtpd.rb +2 -2
  36. data/lib/sisimai/lhost/postfix.rb +25 -27
  37. data/lib/sisimai/lhost/qmail.rb +130 -106
  38. data/lib/sisimai/lhost/sendmail.rb +19 -18
  39. data/lib/sisimai/lhost/v5sendmail.rb +88 -60
  40. data/lib/sisimai/lhost/verizon.rb +2 -2
  41. data/lib/sisimai/lhost/x1.rb +1 -1
  42. data/lib/sisimai/lhost/x2.rb +1 -2
  43. data/lib/sisimai/lhost/x3.rb +2 -2
  44. data/lib/sisimai/lhost/x6.rb +1 -1
  45. data/lib/sisimai/lhost/zoho.rb +2 -2
  46. data/lib/sisimai/lhost.rb +18 -21
  47. data/lib/sisimai/message.rb +93 -146
  48. data/lib/sisimai/order.rb +21 -77
  49. data/lib/sisimai/reason/authfailure.rb +1 -4
  50. data/lib/sisimai/reason/badreputation.rb +2 -2
  51. data/lib/sisimai/reason/blocked.rb +7 -10
  52. data/lib/sisimai/reason/contenterror.rb +7 -1
  53. data/lib/sisimai/reason/exceedlimit.rb +1 -4
  54. data/lib/sisimai/reason/failedstarttls.rb +42 -0
  55. data/lib/sisimai/reason/filtered.rb +5 -4
  56. data/lib/sisimai/reason/hasmoved.rb +1 -2
  57. data/lib/sisimai/reason/hostunknown.rb +3 -3
  58. data/lib/sisimai/reason/mailboxfull.rb +2 -4
  59. data/lib/sisimai/reason/mailererror.rb +1 -2
  60. data/lib/sisimai/reason/mesgtoobig.rb +2 -4
  61. data/lib/sisimai/reason/norelaying.rb +2 -3
  62. data/lib/sisimai/reason/notaccept.rb +2 -3
  63. data/lib/sisimai/reason/notcompliantrfc.rb +10 -4
  64. data/lib/sisimai/reason/rejected.rb +1 -1
  65. data/lib/sisimai/reason/requireptr.rb +2 -2
  66. data/lib/sisimai/reason/securityerror.rb +1 -3
  67. data/lib/sisimai/reason/spamdetected.rb +6 -8
  68. data/lib/sisimai/reason/speeding.rb +1 -2
  69. data/lib/sisimai/reason/suppressed.rb +36 -0
  70. data/lib/sisimai/reason/suspend.rb +1 -3
  71. data/lib/sisimai/reason/systemerror.rb +5 -0
  72. data/lib/sisimai/reason/toomanyconn.rb +1 -2
  73. data/lib/sisimai/reason/userunknown.rb +1 -1
  74. data/lib/sisimai/reason/virusdetected.rb +5 -6
  75. data/lib/sisimai/reason.rb +77 -73
  76. data/lib/sisimai/rfc1123.rb +152 -0
  77. data/lib/sisimai/rfc1894.rb +102 -62
  78. data/lib/sisimai/rfc2045.rb +2 -1
  79. data/lib/sisimai/rfc3464/thirdparty.rb +102 -0
  80. data/lib/sisimai/rfc3464.rb +222 -343
  81. data/lib/sisimai/rfc3834.rb +1 -1
  82. data/lib/sisimai/rfc5322.rb +7 -17
  83. data/lib/sisimai/rfc791.rb +69 -0
  84. data/lib/sisimai/rhost/aol.rb +36 -0
  85. data/lib/sisimai/rhost/apple.rb +5 -2
  86. data/lib/sisimai/rhost/cox.rb +3 -2
  87. data/lib/sisimai/rhost/facebook.rb +100 -0
  88. data/lib/sisimai/rhost/franceptt.rb +3 -2
  89. data/lib/sisimai/rhost/godaddy.rb +3 -2
  90. data/lib/sisimai/rhost/google.rb +19 -17
  91. data/lib/sisimai/rhost/gsuite.rb +42 -0
  92. data/lib/sisimai/rhost/iua.rb +3 -3
  93. data/lib/sisimai/rhost/kddi.rb +3 -2
  94. data/lib/sisimai/rhost/messagelabs.rb +37 -0
  95. data/lib/sisimai/rhost/microsoft.rb +56 -49
  96. data/lib/sisimai/rhost/mimecast.rb +29 -27
  97. data/lib/sisimai/rhost/nttdocomo.rb +4 -3
  98. data/lib/sisimai/rhost/outlook.rb +36 -0
  99. data/lib/sisimai/rhost/spectrum.rb +3 -2
  100. data/lib/sisimai/rhost/tencent.rb +3 -2
  101. data/lib/sisimai/rhost/yahooinc.rb +4 -3
  102. data/lib/sisimai/rhost.rb +69 -39
  103. data/lib/sisimai/smtp/command.rb +31 -21
  104. data/lib/sisimai/smtp/failure.rb +103 -0
  105. data/lib/sisimai/smtp/reply.rb +29 -25
  106. data/lib/sisimai/smtp/status.rb +36 -19
  107. data/lib/sisimai/smtp/transcript.rb +15 -15
  108. data/lib/sisimai/string.rb +0 -46
  109. data/lib/sisimai/version.rb +1 -1
  110. data/set-of-emails/maildir/bsd/lhost-postfix-30.eml +81 -81
  111. data/set-of-emails/maildir/bsd/{lhost-aol-03.eml → rhost-aol-03.eml} +1264 -1264
  112. data/set-of-emails/maildir/bsd/{lhost-aol-04.eml → rhost-aol-04.eml} +1260 -1260
  113. data/set-of-emails/maildir/bsd/{lhost-aol-05.eml → rhost-aol-05.eml} +105 -105
  114. data/set-of-emails/maildir/bsd/{lhost-aol-06.eml → rhost-aol-06.eml} +105 -105
  115. data/set-of-emails/maildir/bsd/rhost-gsuite-01.eml +189 -0
  116. data/set-of-emails/maildir/bsd/rhost-gsuite-02.eml +180 -0
  117. data/set-of-emails/maildir/bsd/rhost-gsuite-03.eml +251 -0
  118. data/set-of-emails/maildir/bsd/rhost-gsuite-04.eml +211 -0
  119. data/set-of-emails/maildir/bsd/rhost-gsuite-05.eml +226 -0
  120. data/set-of-emails/maildir/bsd/rhost-gsuite-06.eml +257 -0
  121. data/set-of-emails/maildir/bsd/rhost-gsuite-07.eml +289 -0
  122. data/set-of-emails/maildir/bsd/rhost-gsuite-08.eml +231 -0
  123. data/set-of-emails/maildir/bsd/rhost-gsuite-09.eml +231 -0
  124. data/set-of-emails/maildir/bsd/rhost-gsuite-10.eml +254 -0
  125. data/set-of-emails/maildir/bsd/rhost-gsuite-11.eml +228 -0
  126. data/set-of-emails/maildir/bsd/rhost-gsuite-12.eml +271 -0
  127. data/set-of-emails/maildir/bsd/rhost-gsuite-13.eml +261 -0
  128. data/set-of-emails/maildir/bsd/rhost-gsuite-14.eml +273 -0
  129. data/set-of-emails/maildir/bsd/rhost-gsuite-15.eml +229 -0
  130. data/set-of-emails/maildir/bsd/{lhost-messagelabs-01.eml → rhost-messagelabs-01.eml} +93 -93
  131. data/set-of-emails/maildir/bsd/rhost-outlook-01.eml +72 -0
  132. data/set-of-emails/maildir/bsd/rhost-outlook-02.eml +72 -0
  133. data/set-of-emails/maildir/bsd/rhost-outlook-03.eml +72 -0
  134. data/set-of-emails/maildir/bsd/rhost-outlook-04.eml +79 -0
  135. data/set-of-emails/maildir/bsd/rhost-outlook-06.eml +75 -0
  136. data/set-of-emails/maildir/bsd/rhost-outlook-07.eml +70 -0
  137. data/set-of-emails/maildir/bsd/rhost-outlook-08.eml +70 -0
  138. data/set-of-emails/maildir/bsd/rhost-outlook-09.eml +56 -0
  139. data/set-of-emails/maildir/tmp/arf-22.eml +49 -0
  140. data/set-of-emails/maildir/tmp/arf-23.eml +49 -0
  141. data/set-of-emails/maildir/tmp/arf-24.eml +50 -0
  142. data/set-of-emails/maildir/tmp/lhost-exim-07.eml +28 -0
  143. metadata +73 -56
  144. data/lib/sisimai/lhost/amavis.rb +0 -163
  145. data/lib/sisimai/lhost/amazonworkmail.rb +0 -127
  146. data/lib/sisimai/lhost/aol.rb +0 -125
  147. data/lib/sisimai/lhost/barracuda.rb +0 -92
  148. data/lib/sisimai/lhost/bigfoot.rb +0 -125
  149. data/lib/sisimai/lhost/facebook.rb +0 -188
  150. data/lib/sisimai/lhost/gsuite.rb +0 -194
  151. data/lib/sisimai/lhost/mailru.rb +0 -214
  152. data/lib/sisimai/lhost/mcafee.rb +0 -109
  153. data/lib/sisimai/lhost/messagelabs.rb +0 -120
  154. data/lib/sisimai/lhost/mxlogic.rb +0 -198
  155. data/lib/sisimai/lhost/office365.rb +0 -252
  156. data/lib/sisimai/lhost/outlook.rb +0 -129
  157. data/lib/sisimai/lhost/powermta.rb +0 -118
  158. data/lib/sisimai/lhost/receivingses.rb +0 -126
  159. data/lib/sisimai/lhost/sendgrid.rb +0 -150
  160. data/lib/sisimai/lhost/surfcontrol.rb +0 -105
  161. data/lib/sisimai/lhost/x4.rb +0 -269
  162. data/lib/sisimai/lhost/x5.rb +0 -112
  163. data/lib/sisimai/lhost/yahoo.rb +0 -102
  164. data/lib/sisimai/lhost/yandex.rb +0 -118
  165. data/lib/sisimai/mda.rb +0 -121
  166. data/lib/sisimai/smtp/error.rb +0 -119
  167. /data/set-of-emails/maildir/bsd/{lhost-googlegroups-15.eml → lhost-googleworkspace-01.eml} +0 -0
  168. /data/set-of-emails/maildir/bsd/{lhost-x4-08.eml → lhost-x2-06.eml} +0 -0
  169. /data/set-of-emails/maildir/bsd/{lhost-gsuite-01.eml → rfc3464-51.eml} +0 -0
  170. /data/set-of-emails/maildir/bsd/{lhost-gsuite-03.eml → rfc3464-52.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{lhost-gsuite-04.eml → rfc3464-53.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{lhost-gsuite-05.eml → rfc3464-54.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{lhost-gsuite-06.eml → rfc3464-55.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{lhost-gsuite-07.eml → rfc3464-56.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{lhost-gsuite-08.eml → rfc3464-57.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{lhost-gsuite-09.eml → rfc3464-58.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{lhost-gsuite-10.eml → rfc3464-59.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{lhost-gsuite-11.eml → rfc3464-60.eml} +0 -0
  179. /data/set-of-emails/maildir/bsd/{lhost-gsuite-12.eml → rfc3464-61.eml} +0 -0
  180. /data/set-of-emails/maildir/bsd/{lhost-gsuite-13.eml → rfc3464-62.eml} +0 -0
  181. /data/set-of-emails/maildir/bsd/{lhost-gsuite-14.eml → rfc3464-63.eml} +0 -0
  182. /data/set-of-emails/maildir/bsd/{lhost-gsuite-15.eml → rfc3464-64.eml} +0 -0
  183. /data/set-of-emails/maildir/bsd/{lhost-gsuite-02.eml → rfc3464-65.eml} +0 -0
  184. /data/set-of-emails/maildir/bsd/{lhost-aol-01.eml → rhost-aol-01.eml} +0 -0
  185. /data/set-of-emails/maildir/bsd/{lhost-aol-02.eml → rhost-aol-02.eml} +0 -0
  186. /data/set-of-emails/maildir/bsd/{lhost-facebook-03.eml → rhost-facebook-03.eml} +0 -0
  187. /data/set-of-emails/maildir/bsd/{lhost-facebook-04.eml → rhost-facebook-04.eml} +0 -0
  188. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-02.eml → rhost-messagelabs-02.eml} +0 -0
  189. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-03.eml → rhost-messagelabs-03.eml} +0 -0
  190. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-37.eml +0 -0
  191. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-38.eml +0 -0
  192. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-39.eml +0 -0
@@ -1,19 +1,29 @@
1
1
  module Sisimai
2
2
  # Sisimai::Reason detects the bounce reason from the Hash table which is to be constructed to
3
- # Sisimai::Fact object as an argument of get() method. This class is called only Sisimai::Fact class.
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
6
  # All the error reason list Sisimai support
7
7
  # @return [Array] Reason list
8
8
  def index
9
9
  return %w[
10
- AuthFailure BadReputation Blocked ContentError ExceedLimit Expired Filtered HasMoved
11
- HostUnknown MailboxFull MailerError MesgTooBig NetworkError NotAccept NotCompliantRFC
12
- OnHold Rejected NoRelaying Speeding SpamDetected VirusDetected PolicyViolation
13
- SecurityError Suspend RequirePTR SystemError SystemFull TooManyConn UserUnknown SyntaxError
10
+ AuthFailure BadReputation Blocked ContentError ExceedLimit Expired FailedSTARTTLS Filtered
11
+ HasMoved HostUnknown MailboxFull MailerError MesgTooBig NetworkError NotAccept NotCompliantRFC
12
+ OnHold Rejected NoRelaying Speeding SpamDetected VirusDetected PolicyViolation SecurityError
13
+ Suspend RequirePTR SystemError SystemFull TooManyConn Suppressed UserUnknown SyntaxError
14
14
  ]
15
15
  end
16
16
 
17
+ # @abstract is_explicit() returns 0 when the argument is empty or is "undefined" or is "onhold"
18
+ # @param string argv1 Reason name
19
+ # @return bool false: The reaosn is not explicit
20
+ def is_explicit(argv1 = '')
21
+ return false if argv1.nil?
22
+ return false if argv1.empty?
23
+ return false if argv1 == "undefined" || argv1 == "onhold" || argv1.empty?
24
+ return true
25
+ end
26
+
17
27
  # @abstract Returns Sisimai::Reason::* module path table
18
28
  # @return [Hash] Module path table
19
29
  # @since v4.25.6
@@ -29,8 +39,7 @@ module Sisimai
29
39
  def retry
30
40
  return {
31
41
  'undefined' => true, 'onhold' => true, 'systemerror' => true, 'securityerror' => true,
32
- 'expired' => true, 'suspend' => true, 'networkerror' => true, 'hostunknown' => true,
33
- 'userunknown' => true
42
+ 'expired' => true, 'networkerror' => true, 'hostunknown' => true, 'userunknown' => true
34
43
  }.freeze
35
44
  end
36
45
  ModulePath = Sisimai::Reason.path
@@ -38,19 +47,19 @@ module Sisimai
38
47
  ClassOrder = [
39
48
  %w[
40
49
  MailboxFull MesgTooBig ExceedLimit Suspend HasMoved NoRelaying AuthFailure UserUnknown
41
- Filtered RequirePTR NotCompliantRFC BadReputation Rejected HostUnknown SpamDetected Speeding
42
- TooManyConn Blocked
50
+ Filtered RequirePTR NotCompliantRFC BadReputation ContentError Rejected HostUnknown
51
+ SpamDetected Speeding TooManyConn Blocked
43
52
  ],
44
53
  %w[
45
54
  MailboxFull AuthFailure BadReputation Speeding SpamDetected VirusDetected PolicyViolation
46
55
  NoRelaying SystemError NetworkError Suspend ContentError SystemFull NotAccept Expired
47
- SecurityError MailerError
56
+ FailedSTARTTLS SecurityError Suppressed MailerError
48
57
  ],
49
58
  %w[
50
59
  MailboxFull MesgTooBig ExceedLimit Suspend UserUnknown Filtered Rejected HostUnknown
51
- SpamDetected Speeding TooManyConn Blocked SpamDetected AuthFailure SecurityError
52
- SystemError NetworkError Suspend Expired ContentError HasMoved SystemFull NotAccept
53
- MailerError NoRelaying SyntaxError OnHold
60
+ SpamDetected Speeding TooManyConn Blocked SpamDetected AuthFailure FailedSTARTTLS
61
+ SecurityError SystemError NetworkError Suspend Expired ContentError HasMoved SystemFull
62
+ NotAccept MailerError NoRelaying Suppressed SyntaxError OnHold
54
63
  ]
55
64
  ]
56
65
 
@@ -58,7 +67,7 @@ module Sisimai
58
67
  # @param [Hash] argvs Decoded email object
59
68
  # @return [String, nil] Bounce reason or nil if the argument is missing or not Hash
60
69
  # @see anotherone
61
- def get(argvs)
70
+ def find(argvs)
62
71
  return nil unless argvs
63
72
  unless GetRetried[argvs['reason']]
64
73
  # Return reason text already decided except reason match with the regular expression of
@@ -111,10 +120,10 @@ module Sisimai
111
120
  return reasontext
112
121
  end
113
122
 
114
- # Detect the other bounce reason, fall back method for get()
123
+ # Detect the other bounce reason, fall back method for find()
115
124
  # @param [Hash] argvs Decoded email object
116
125
  # @return [String, Nil] Bounce reason or nli if the argument is missing or not Hash
117
- # @see get
126
+ # @see find()
118
127
  def anotherone(argvs)
119
128
  return argvs['reason'] unless argvs['reason'].empty?
120
129
 
@@ -124,65 +133,60 @@ module Sisimai
124
133
  actiontext = argvs['action'] || ''
125
134
  statuscode = argvs['deliverystatus'] || ''
126
135
  reasontext = Sisimai::SMTP::Status.name(statuscode) || ''
136
+ trytomatch = reasontext.empty? ? true : false
137
+ trytomatch ||= true if GetRetried[reasontext] || codeformat != 'SMTP'
127
138
 
128
- catch :TRY_TO_MATCH do
129
- while true
130
- trytomatch = reasontext.empty? ? true : false
131
- trytomatch ||= true if GetRetried[reasontext]
132
- trytomatch ||= true if codeformat != 'SMTP'
133
- throw :TRY_TO_MATCH unless trytomatch
134
-
135
- # Could not decide the reason by the value of Status:
136
- ClassOrder[1].each do |e|
137
- # Trying to match with other patterns in Sisimai::Reason::* classes
138
- p = 'Sisimai::Reason::' << e
139
- r = nil
140
- begin
141
- require ModulePath[p]
142
- r = Module.const_get(p)
143
- rescue
144
- warn ' ***warning: Failed to load ' << p
145
- next
146
- end
147
-
148
- next unless r.match(issuedcode)
149
- reasontext = e.downcase
150
- break
151
- end
152
- throw :TRY_TO_MATCH unless reasontext.empty?
153
-
154
- # Check the value of Status:
155
- code2digit = statuscode[0, 3] || ''
156
- if code2digit == '5.6' || code2digit == '4.6'
157
- # X.6.0 Other or undefined media error
158
- reasontext = 'contenterror'
159
-
160
- elsif code2digit == '5.7' || code2digit == '4.7'
161
- # X.7.0 Other or undefined security status
162
- reasontext = 'securityerror'
163
-
164
- elsif codeformat.start_with?('X-UNIX')
165
- # Diagnostic-Code: X-UNIX; ...
166
- reasontext = 'mailererror'
167
-
168
- else
169
- # 50X Syntax Error?
170
- require 'sisimai/reason/syntaxerror'
171
- reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
172
- end
173
- throw :TRY_TO_MATCH unless reasontext.empty?
174
-
175
- # Check the value of Action: field, first
176
- if actiontext.start_with?('delayed', 'expired')
177
- # Action: delayed, expired
178
- reasontext = 'expired'
179
- else
180
- # Rejected at connection or after EHLO|HELO
181
- thecommand = argvs['smtpcommand'] || ''
182
- reasontext = 'blocked' if %w[HELO EHLO].index(thecommand)
139
+ while trytomatch
140
+ # Could not decide the reason by the value of Status:
141
+ ClassOrder[1].each do |e|
142
+ # Trying to match with other patterns in Sisimai::Reason::* classes
143
+ p = 'Sisimai::Reason::' << e
144
+ r = nil
145
+ begin
146
+ require ModulePath[p]
147
+ r = Module.const_get(p)
148
+ rescue
149
+ warn ' ***warning: Failed to load ' << p
150
+ next
183
151
  end
184
- throw :TRY_TO_MATCH
152
+
153
+ next unless r.match(issuedcode)
154
+ reasontext = e.downcase
155
+ break
156
+ end
157
+ break unless reasontext.empty?
158
+
159
+ # Check the value of Status:
160
+ code2digit = statuscode[0, 3] || ''
161
+ if code2digit == '5.6' || code2digit == '4.6'
162
+ # X.6.0 Other or undefined media error
163
+ reasontext = 'contenterror'
164
+
165
+ elsif code2digit == '5.7' || code2digit == '4.7'
166
+ # X.7.0 Other or undefined security status
167
+ reasontext = 'securityerror'
168
+
169
+ elsif codeformat.start_with?('X-UNIX')
170
+ # Diagnostic-Code: X-UNIX; ...
171
+ reasontext = 'mailererror'
172
+
173
+ else
174
+ # 50X Syntax Error?
175
+ require 'sisimai/reason/syntaxerror'
176
+ reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
185
177
  end
178
+ break unless reasontext.empty?
179
+
180
+ # Check the value of Action: field, first
181
+ if actiontext.start_with?('delayed', 'expired')
182
+ # Action: delayed, expired
183
+ reasontext = 'expired'
184
+ else
185
+ # Rejected at connection or after EHLO|HELO
186
+ thecommand = argvs['command'] || ''
187
+ reasontext = 'blocked' if %w[HELO EHLO].index(thecommand)
188
+ end
189
+ break
186
190
  end
187
191
  return reasontext
188
192
  end
@@ -222,7 +226,7 @@ module Sisimai
222
226
  else
223
227
  # Detect the bounce reason from "Status:" code
224
228
  require 'sisimai/smtp/status'
225
- cv = Sisimai::SMTP::Status.find(argv1) || ''
229
+ cv = Sisimai::SMTP::Status.find(argv1)
226
230
  reasontext = Sisimai::SMTP::Status.name(cv) || 'undefined'
227
231
  end
228
232
  return reasontext
@@ -0,0 +1,152 @@
1
+ module Sisimai
2
+ # Sisimai::RFC1123 is a class related to the Internet host
3
+ module RFC1123
4
+ class << self
5
+ require 'sisimai/string'
6
+ Sandwiched = [
7
+ # (Postfix) postfix/src/smtp/smtp_proto.c: "host %s said: %s (in reply to %s)",
8
+ # - <kijitora@example.com>: host re2.example.com[198.51.100.2] said: 550 ...
9
+ # - <kijitora@example.org>: host r2.example.org[198.51.100.18] refused to talk to me:
10
+ ["host ", " said: "],
11
+ ["host ", " talk to me: "],
12
+ ["while talking to ", ":"], # (Sendmail) ... while talking to mx.bouncehammer.jp.:
13
+ ["host ", " ["], # (Exim) host mx.example.jp [192.0.2.20]: 550 5.7.0
14
+ [" by ", ". ["], # (Gmail) ...for the recipient domain example.jp by mx.example.jp. [192.0.2.1].
15
+
16
+ # (MailFoundry)
17
+ # - Delivery failed for the following reason: Server mx22.example.org[192.0.2.222] failed with: 550...
18
+ # - Delivery failed for the following reason: mail.example.org[192.0.2.222] responded with failure: 552..
19
+ ["delivery failed for the following reason: ", " with"],
20
+ ["remote system: ", "("], # (MessagingServer) Remote system: dns;mx.example.net (mx. --
21
+ ["smtp server <", ">"], # (X6) SMTP Server <smtpd.libsisimai.org> rejected recipient ...
22
+ ["-mta: ", ">"], # (MailMarshal) Reporting-MTA: <rr1.example.com>
23
+ [" : ", "["], # (SendGrid) cat:000000:<cat@example.jp> : 192.0.2.1 : mx.example.jp:[192.0.2.2]...
24
+ ].freeze
25
+ StartAfter = [
26
+ "generating server: ", # (Exchange2007) en-US/Generating server: mta4.example.org
27
+ "serveur de g", # (Exchange2007) fr-FR/Serveur de g辿n辿ration
28
+ "server di generazione", # (Exchange2007) it-CH
29
+ "genererande server", # (Exchange2007) sv-SE
30
+ ].freeze
31
+ ExistUntil = [
32
+ " did not like our ", # (Dragonfly) mail-inbound.libsisimai.net [192.0.2.25] did not like our DATA: ...
33
+ ].freeze
34
+
35
+ # Returns "true" when the given string is a valid internet host
36
+ # @param [String] argv0 Hostname
37
+ # @return [Boolean] false: is not a valid internet host, true: is a valid interneet host
38
+ # @since v5.2.0
39
+ def is_internethost(argv0 = '')
40
+ return false unless argv0
41
+ return false if argv0.size < 4
42
+ return false if argv0.size > 255
43
+ return false if argv0.include?(".") == false
44
+ return false if argv0.include?("..")
45
+ return false if argv0.include?("--")
46
+ return false if argv0.start_with?(".")
47
+ return false if argv0.start_with?("-")
48
+ return false if argv0.end_with?("-")
49
+
50
+ hostnameok = true
51
+ characters = argv0.upcase.split("")
52
+ characters.each do |e|
53
+ # Check each characater is a number or an alphabet
54
+ f = e.ord
55
+ if f < 45 then hostnameok = false; break; end # 45 = '-'
56
+ if f == 47 then hostnameok = false; break; end # 47 = '/'
57
+ if f > 57 && f < 65 then hostnameok = false; break; end # 57 = '9', 65 = 'A'
58
+ if f > 90 then hostnameok = false; break; end # 90 = 'Z'
59
+ end
60
+ return false if hostnameok == false
61
+
62
+ p1 = argv0.rindex(".")
63
+ cv = argv0[p1 + 1, argv0.size - p1]; return false if cv.size > 63
64
+ cv.split("").each do |e|
65
+ # The top level domain should not include a number
66
+ f = e.ord
67
+ if f > 47 && f < 58 then hostnameok = false; break; end
68
+ end
69
+ return hostnameok
70
+ end
71
+
72
+ # find() returns a valid internet hostname found from the argument
73
+ # @param string argv1 String including hostnames
74
+ # @return string A valid internet hostname found in the argument
75
+ # @since v5.2.0
76
+ def find(argv1 = "")
77
+ return "" unless argv1
78
+ return "" unless argv1.size > 4
79
+
80
+ sourcetext = argv1.downcase
81
+ sourcelist = []
82
+ foundtoken = []
83
+ thelongest = 0
84
+ hostnameis = ""
85
+
86
+ # Replace some string for splitting by " "
87
+ # - mx.example.net[192.0.2.1] => mx.example.net [192.0.2.1]
88
+ # - mx.example.jp:[192.0.2.1] => mx.example.jp :[192.0.2.1]
89
+ sourcetext = sourcetext.gsub("[", " [").gsub("(", " (").gsub("<", " <") # Prefix a space character before each bracket
90
+ sourcetext = sourcetext.gsub("]", "] ").gsub(")", ") ").gsub(">", "> ") # Suffix a space character behind each bracket
91
+ sourcetext = sourcetext.gsub(":", ": ").gsub(";", "; ") # Suffix a space character behind : and ;
92
+ sourcetext = Sisimai::String.sweep(sourcetext)
93
+
94
+ catch :MAKELIST do
95
+ Sandwiched.each do |e|
96
+ # Check a hostname exists between the $e->[0] and $e->[1] at array "Sandwiched"
97
+ # Each array in Sandwiched have 2 elements
98
+ next unless Sisimai::String.aligned(sourcetext, e)
99
+
100
+ p1 = sourcetext.index(e[0])
101
+ p2 = sourcetext.index(e[1])
102
+ cw = e[0].size
103
+ next if p1 + cw >= p2
104
+
105
+ sourcelist = sourcetext[p1 + cw, p2 - cw - p1].split(" ")
106
+ throw :MAKELIST
107
+ end
108
+
109
+ # Check other patterns which are not sandwiched
110
+ StartAfter.each do |e|
111
+ # StartAfter have some strings, not an array
112
+ p1 = sourcetext.index(e); next if p1.nil?
113
+ cw = e.size
114
+ sourcelist = sourcetext[p1 + cw, sourcetext.size].split(" ")
115
+ throw :MAKELIST
116
+ end
117
+ ExistUntil.each do |e|
118
+ # ExistUntil have some strings, not an array
119
+ p1 = sourcetext.index(e); next unless p1
120
+ sourcelist = sourcetext[0, p1].split(" ")
121
+ throw :MAKELIST
122
+ end
123
+
124
+ sourcelist = sourcetext.split(" ") if sourcelist.size == 0
125
+ throw :MAKELIST
126
+ end
127
+
128
+ sourcelist.each do |e|
129
+ # Pick some strings which is 4 or more length, is including "." character
130
+ e.chop! if e[-1, 1] == "." # Remove "." at the end of the string
131
+ e.delete!('[]()<>:;')
132
+
133
+ next unless e.size > 3
134
+ next unless e.include?(".")
135
+ next unless Sisimai::RFC1123.is_internethost(e)
136
+ foundtoken << e
137
+ end
138
+ return "" if foundtoken.size == 0
139
+ return foundtoken[0] if foundtoken.size == 1
140
+
141
+ foundtoken.each do |e|
142
+ # Returns the longest hostname
143
+ cw = e.size; next if thelongest >= cw
144
+ hostnameis = e
145
+ thelongest = cw
146
+ end
147
+ return hostnameis
148
+ end
149
+ end
150
+ end
151
+ end
152
+
@@ -2,7 +2,8 @@ module Sisimai
2
2
  # Sisimai::RFC1894 DSN field defined in RFC3464 (obsoletes RFC1894)
3
3
  module RFC1894
4
4
  class << self
5
- FieldNames = {
5
+ require 'sisimai/string'
6
+ FieldNames = [
6
7
  # https://tools.ietf.org/html/rfc3464#section-2.2
7
8
  # Some fields of a DSN apply to all of the delivery attempts described by that DSN. At
8
9
  # most, these fields may appear once in any DSN. These fields are used to correlate the
@@ -15,10 +16,12 @@ module Sisimai
15
16
  # The following fields are not used in Sisimai:
16
17
  # - Original-Envelope-Id
17
18
  # - DSN-Gateway
18
- 'arrival-date' => ':',
19
- 'received-from-mta' => ';',
20
- 'reporting-mta' => ';',
21
- 'x-original-message-id' => '@',
19
+ {
20
+ 'arrival-date' => ':',
21
+ 'received-from-mta' => ';',
22
+ 'reporting-mta' => ';',
23
+ 'x-original-message-id' => '@',
24
+ },
22
25
 
23
26
  # https://tools.ietf.org/html/rfc3464#section-2.3
24
27
  # A DSN contains information about attempts to deliver a message to one or more recipi-
@@ -32,28 +35,31 @@ module Sisimai
32
35
  # The following fields are not used in Sisimai:
33
36
  # - Will-Retry-Until
34
37
  # - Final-Log-ID
35
- 'action' => 'e',
36
- 'diagnostic-code' => ';',
37
- 'final-recipient' => ';',
38
- 'last-attempt-date' => ':',
39
- 'original-recipient' => ';',
40
- 'remote-mta' => ';',
41
- 'status' => '.',
42
- 'x-actual-recipient' => ';',
43
- }.freeze
38
+ {
39
+ 'action' => 'e',
40
+ 'diagnostic-code' => ';',
41
+ 'final-recipient' => ';',
42
+ 'last-attempt-date' => ':',
43
+ 'original-recipient' => ';',
44
+ 'remote-mta' => ';',
45
+ 'status' => '.',
46
+ 'x-actual-recipient' => ';',
47
+ }
48
+ ].freeze
44
49
 
45
50
  CapturesOn = {
46
- 'addr' => %r/\A((?:Original|Final|X-Actual)-Recipient):[ ](.+?);[ ]*(.+)/,
47
- 'code' => %r/\A(Diagnostic-Code):[ ](.+?);[ ]*(.*)/,
48
- 'date' => %r/\A((?:Arrival|Last-Attempt)-Date):[ ](.+)/,
49
- 'host' => %r/\A((?:Received-From|Remote|Reporting)-MTA):[ ](.+?);[ ]*(.+)/,
50
- 'list' => %r/\A(Action):[ ](delayed|deliverable|delivered|expanded|expired|failed|failure|relayed)/i,
51
- 'stat' => %r/\A(Status):[ ]([245][.]\d+[.]\d+)/,
52
- 'text' => %r/\A(X-Original-Message-ID):[ ](.+)/,
53
- #'text' => %r/\A(Final-Log-ID|Original-Envelope-Id):[ ]*(.+)/,
51
+ "addr" => ["Final-Recipient", "Original-Recipient", "X-Actual-Recipient"],
52
+ "code" => ["Diagnostic-Code"],
53
+ "date" => ["Arrival-Date", "Last-Attempt-Date"],
54
+ "host" => ["Received-From-MTA", "Remote-MTA", "Reporting-MTA"],
55
+ "list" => ["Action"],
56
+ "stat" => ["Status"],
57
+ #"text" => ["X-Original-Message-ID", "Final-Log-ID", "Original-Envelope-ID"],
54
58
  }.freeze
55
59
 
56
- Correction = { action: { 'deliverable' => 'delivered', 'expired' => 'delayed', 'failure' => 'failed' }}
60
+ SubtypeSet = { "addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS" }.freeze
61
+ ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
62
+ Correction = { 'deliverable' => 'delivered', 'expired' => 'delayed', 'failure' => 'failed' }
57
63
  FieldGroup = {
58
64
  'original-recipient' => 'addr',
59
65
  'final-recipient' => 'addr',
@@ -72,7 +78,7 @@ module Sisimai
72
78
  def FIELDINDEX
73
79
  return %w[
74
80
  Action Arrival-Date Diagnostic-Code Final-Recipient Last-Attempt-Date Original-Recipient
75
- Received-From-MTA Remote-MTA Reporting-MTA Status X-Actual-Recipienet X-Original-Message-ID
81
+ Received-From-MTA Remote-MTA Reporting-MTA Status X-Actual-Recipient X-Original-Message-ID
76
82
  ]
77
83
  end
78
84
 
@@ -89,7 +95,7 @@ module Sisimai
89
95
  'original-recipient' => 'alias',
90
96
  'received-from-mta' => 'lhost',
91
97
  'remote-mta' => 'rhost',
92
- 'reporting-mta' => 'rhost',
98
+ 'reporting-mta' => 'lhost',
93
99
  'status' => 'status',
94
100
  'x-actual-recipient' => 'alias',
95
101
  }
@@ -97,18 +103,34 @@ module Sisimai
97
103
 
98
104
  # Check the argument matches with a field defined in RFC3464
99
105
  # @param [String] argv0 A line including field and value defined in RFC3464
100
- # @return [Boolean] false: did not matched, true: matched
106
+ # @return [Integer] 0: did not matched
107
+ # 1: Matched with per-message field
108
+ # 2: Matched with per-recipient field
101
109
  # @since v4.25.0
102
110
  def match(argv0 = '')
103
- label = Sisimai::RFC1894.label(argv0)
111
+ return 0 unless argv0
112
+ return 0 unless argv0.size > 0
113
+ label = Sisimai::RFC1894.label(argv0); return 0 unless label
114
+ match = 0
104
115
 
105
- return false unless label
106
- return false unless FieldNames.has_key?(label)
107
- return false unless argv0.include?(FieldNames[label])
108
- return true
116
+ FieldNames[0].each_key do |e|
117
+ # Per-Message fields
118
+ next unless label == e
119
+ next unless argv0.include?(FieldNames[0][label])
120
+ match = 1; break
121
+ end
122
+ return match if match > 0
123
+
124
+ FieldNames[1].each_key do |e|
125
+ # Per-Recipient field
126
+ next unless label == e
127
+ next unless argv0.include?(FieldNames[1][label])
128
+ match = 2; break
129
+ end
130
+ return match
109
131
  end
110
132
 
111
- # Returns a field name as a lqbel from the given string
133
+ # Returns a field name as a label from the given string
112
134
  # @param [String] argv0 A line including field and value defined in RFC3464
113
135
  # @return [String] Field name as a label
114
136
  # @since v4.25.15
@@ -125,43 +147,61 @@ module Sisimai
125
147
  return nil if argv0.empty?
126
148
  label = Sisimai::RFC1894.label(argv0)
127
149
  group = FieldGroup[label] || ''
150
+ parts = argv0.split(":", 2); parts[1] = parts[1].nil? ? "" : Sisimai::String.sweep(parts[1])
128
151
 
129
152
  return nil if group.empty?
130
153
  return nil unless CapturesOn[group]
131
154
 
132
- table = ['', '', '', '']
133
- match = false
134
- while cv = argv0.match(CapturesOn[group])
135
- # Try to match with each pattern of Per-Message field, Per-Recipient field
136
- # - 0: Field-Name
137
- # - 1: Sub Type: RFC822, DNS, X-Unix, and so on)
138
- # - 2: Value
139
- # - 3: Field Group(addr, code, date, host, stat, text)
140
- match = true
141
- table[0] = cv[1].downcase
142
- table[3] = group
143
-
144
- if group == 'addr' || group == 'code' || group == 'host'
145
- # - Final-Recipient: RFC822; kijitora@nyaan.jp
146
- # - Diagnostic-Code: SMTP; 550 5.1.1 <kijitora@example.jp>... User Unknown
147
- # - Remote-MTA: DNS; mx.example.jp
148
- table[1] = cv[2].upcase
149
- table[2] = group == 'host' ? cv[3].downcase : cv[3]
150
- table[2] = '' if table[2] =~ /\A\s+\z/ # Remote-MTA: dns;
155
+ # Try to match with each pattern of Per-Message field, Per-Recipient field
156
+ # - 0: Field-Name
157
+ # - 1: Sub Type: RFC822, DNS, X-Unix, and so on)
158
+ # - 2: Value
159
+ # - 3: Field Group(addr, code, date, host, stat, text)
160
+ # - 4: Comment
161
+ table = [label, "", "", group, ""]
162
+
163
+ if group == 'addr' || group == 'code' || group == 'host'
164
+ # - Final-Recipient: RFC822; kijitora@nyaan.jp
165
+ # - Diagnostic-Code: SMTP; 550 5.1.1 <kijitora@example.jp>... User Unknown
166
+ # - Remote-MTA: DNS; mx.example.jp
167
+ if parts[1].include?(";")
168
+ # There is a valid sub type (including ";")
169
+ v = parts[1].split(";", 2)
170
+ table[1] = Sisimai::String.sweep(v[0]).upcase if v.size > 0
171
+ table[2] = Sisimai::String.sweep(v[1]) if v.size > 1
151
172
  else
152
- # - Action: failed
153
- # - Status: 5.2.2
154
- table[1] = ''
155
- table[2] = group == 'date' ? cv[2] : cv[2].downcase
156
-
157
- # Correct invalid value in Action field:
158
- break unless group == 'list'
159
- break unless Correction[:action][table[2]]
160
- table[2] = Correction[:action][table[2]]
173
+ # There is no sub type like "Diagnostic-Code: 550 5.1.1 <kijitora@example.jp>..."
174
+ table[2] = Sisimai::String.sweep(parts[1])
175
+ table[1] = SubtypeSet[group] || ""
161
176
  end
162
- break
177
+ table[2] = table[2].downcase if group == "host"
178
+ table[2] = "" if table[2] =~ /\A\s+\z/
179
+
180
+ elsif group == "list"
181
+ # Action: failed
182
+ # Check that the value is an available value defined in "ActionList" or not.
183
+ # When the value is invalid, convert to an available value defined in "Correction"
184
+ v = parts[1].downcase
185
+ table[2] = v if ActionList.any? { |a| v == a }
186
+ table[2] = Correction[v] if table[2].empty?
187
+
188
+ else
189
+ # Other groups such as Status:, Arrival-Date:, or X-Original-Message-ID:.
190
+ # There is no ";" character in the field.
191
+ # - Status: 5.2.2
192
+ # - Arrival-Date: Mon, 21 May 2018 16:09:59 +0900
193
+ table[2] = group == "date" ? parts[1] : parts[1].downcase
163
194
  end
164
- return nil unless match
195
+
196
+ if Sisimai::String.aligned(table[2], [" (", ")"])
197
+ # Extract text enclosed in parentheses as comments
198
+ # Reporting-MTA: dns; mr21p30im-asmtp004.me.example.com (tcp-daemon)
199
+ p1 = table[2].index(" (")
200
+ p2 = table[2].index(")")
201
+ table[4] = table[2][p1 + 2, p2 - p1 - 2]
202
+ table[2] = table[2][0, p1]
203
+ end
204
+
165
205
  return table
166
206
  end
167
207
 
@@ -277,6 +277,7 @@ module Sisimai
277
277
  iso2022set = %r/charset=["']?(iso-2022-[-a-z0-9]+)['"]?\b/
278
278
  multiparts = levelout(argv0, argv1)
279
279
  flattenout = ''
280
+ delimiters = ["/delivery-status", "/rfc822", "/feedback-report", "/partial"]
280
281
 
281
282
  while e = multiparts.shift do
282
283
  # Pick only the following parts Sisimai::Lhost will use, and decode each part
@@ -352,7 +353,7 @@ module Sisimai
352
353
  bodystring << bodyinside
353
354
  end
354
355
 
355
- if mediatypev.include?('/delivery-status') || mediatypev.include?('/feedback-report') || mediatypev.include?('/rfc822')
356
+ if delimiters.any? { |a| mediatypev.include?(a) }
356
357
  # Add Content-Type: header of each part (will be used as a delimiter at Sisimai::Lhost)
357
358
  # into the body inside when the value of Content-Type: field is message/delivery-status,
358
359
  # message/rfc822, or text/rfc822-headers