sisimai 4.25.16-java → 5.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +3 -3
  3. data/ANALYTICAL-PRECISION +2 -2
  4. data/Benchmarks.mk +3 -3
  5. data/CONTRIBUTING +1 -1
  6. data/ChangeLog.md +412 -393
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +15 -15
  10. data/README-JA.md +140 -78
  11. data/README.md +290 -143
  12. data/Rakefile +9 -3
  13. data/Repository.mk +2 -3
  14. data/lib/sisimai/address.rb +118 -74
  15. data/lib/sisimai/arf.rb +84 -82
  16. data/lib/sisimai/datetime.rb +5 -52
  17. data/lib/sisimai/{data → fact}/json.rb +7 -9
  18. data/lib/sisimai/fact/yaml.rb +31 -0
  19. data/lib/sisimai/fact.rb +468 -0
  20. data/lib/sisimai/lhost/activehunter.rb +12 -14
  21. data/lib/sisimai/lhost/amavis.rb +11 -14
  22. data/lib/sisimai/lhost/amazonses.rb +37 -41
  23. data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
  24. data/lib/sisimai/lhost/aol.rb +12 -14
  25. data/lib/sisimai/lhost/apachejames.rb +19 -21
  26. data/lib/sisimai/lhost/barracuda.rb +10 -12
  27. data/lib/sisimai/lhost/bigfoot.rb +21 -21
  28. data/lib/sisimai/lhost/biglobe.rb +15 -16
  29. data/lib/sisimai/lhost/courier.rb +20 -20
  30. data/lib/sisimai/lhost/domino.rb +23 -19
  31. data/lib/sisimai/lhost/einsundeins.rb +23 -18
  32. data/lib/sisimai/lhost/exchange2003.rb +30 -29
  33. data/lib/sisimai/lhost/exchange2007.rb +70 -58
  34. data/lib/sisimai/lhost/exim.rb +175 -161
  35. data/lib/sisimai/lhost/ezweb.rb +31 -56
  36. data/lib/sisimai/lhost/facebook.rb +21 -33
  37. data/lib/sisimai/lhost/fml.rb +43 -48
  38. data/lib/sisimai/lhost/gmail.rb +29 -29
  39. data/lib/sisimai/lhost/gmx.rb +18 -17
  40. data/lib/sisimai/lhost/googlegroups.rb +9 -10
  41. data/lib/sisimai/lhost/gsuite.rb +21 -27
  42. data/lib/sisimai/lhost/imailserver.rb +25 -39
  43. data/lib/sisimai/lhost/interscanmss.rb +28 -31
  44. data/lib/sisimai/lhost/kddi.rb +22 -28
  45. data/lib/sisimai/lhost/mailfoundry.rb +11 -12
  46. data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
  47. data/lib/sisimai/lhost/mailru.rb +33 -27
  48. data/lib/sisimai/lhost/mcafee.rb +21 -31
  49. data/lib/sisimai/lhost/messagelabs.rb +17 -20
  50. data/lib/sisimai/lhost/messagingserver.rb +40 -37
  51. data/lib/sisimai/lhost/mfilter.rb +15 -16
  52. data/lib/sisimai/lhost/mxlogic.rb +24 -23
  53. data/lib/sisimai/lhost/notes.rb +17 -17
  54. data/lib/sisimai/lhost/office365.rb +63 -27
  55. data/lib/sisimai/lhost/opensmtpd.rb +12 -13
  56. data/lib/sisimai/lhost/outlook.rb +12 -15
  57. data/lib/sisimai/lhost/postfix.rb +179 -129
  58. data/lib/sisimai/lhost/powermta.rb +12 -14
  59. data/lib/sisimai/lhost/qmail.rb +44 -47
  60. data/lib/sisimai/lhost/receivingses.rb +15 -20
  61. data/lib/sisimai/lhost/sendgrid.rb +34 -32
  62. data/lib/sisimai/lhost/sendmail.rb +66 -53
  63. data/lib/sisimai/lhost/surfcontrol.rb +19 -19
  64. data/lib/sisimai/lhost/v5sendmail.rb +45 -39
  65. data/lib/sisimai/lhost/verizon.rb +35 -39
  66. data/lib/sisimai/lhost/x1.rb +18 -17
  67. data/lib/sisimai/lhost/x2.rb +17 -14
  68. data/lib/sisimai/lhost/x3.rb +19 -19
  69. data/lib/sisimai/lhost/x4.rb +72 -57
  70. data/lib/sisimai/lhost/x5.rb +17 -19
  71. data/lib/sisimai/lhost/x6.rb +41 -17
  72. data/lib/sisimai/lhost/yahoo.rb +17 -16
  73. data/lib/sisimai/lhost/yandex.rb +16 -20
  74. data/lib/sisimai/lhost/zoho.rb +16 -15
  75. data/lib/sisimai/lhost.rb +8 -10
  76. data/lib/sisimai/mail/maildir.rb +1 -3
  77. data/lib/sisimai/mail/mbox.rb +3 -4
  78. data/lib/sisimai/mail/memory.rb +0 -1
  79. data/lib/sisimai/mail/stdin.rb +1 -3
  80. data/lib/sisimai/mail.rb +3 -7
  81. data/lib/sisimai/mda.rb +28 -42
  82. data/lib/sisimai/message.rb +435 -326
  83. data/lib/sisimai/order.rb +5 -5
  84. data/lib/sisimai/reason/authfailure.rb +64 -0
  85. data/lib/sisimai/reason/badreputation.rb +53 -0
  86. data/lib/sisimai/reason/blocked.rb +94 -160
  87. data/lib/sisimai/reason/contenterror.rb +8 -9
  88. data/lib/sisimai/reason/delivered.rb +4 -6
  89. data/lib/sisimai/reason/exceedlimit.rb +10 -12
  90. data/lib/sisimai/reason/expired.rb +6 -8
  91. data/lib/sisimai/reason/feedback.rb +2 -3
  92. data/lib/sisimai/reason/filtered.rb +17 -19
  93. data/lib/sisimai/reason/hasmoved.rb +9 -10
  94. data/lib/sisimai/reason/hostunknown.rb +15 -15
  95. data/lib/sisimai/reason/mailboxfull.rb +10 -12
  96. data/lib/sisimai/reason/mailererror.rb +18 -20
  97. data/lib/sisimai/reason/mesgtoobig.rb +9 -11
  98. data/lib/sisimai/reason/networkerror.rb +5 -8
  99. data/lib/sisimai/reason/norelaying.rb +8 -11
  100. data/lib/sisimai/reason/notaccept.rb +13 -14
  101. data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
  102. data/lib/sisimai/reason/onhold.rb +6 -9
  103. data/lib/sisimai/reason/policyviolation.rb +14 -12
  104. data/lib/sisimai/reason/rejected.rb +26 -24
  105. data/lib/sisimai/reason/requireptr.rb +69 -0
  106. data/lib/sisimai/reason/securityerror.rb +33 -36
  107. data/lib/sisimai/reason/spamdetected.rb +114 -147
  108. data/lib/sisimai/reason/speeding.rb +49 -0
  109. data/lib/sisimai/reason/suspend.rb +11 -11
  110. data/lib/sisimai/reason/syntaxerror.rb +11 -10
  111. data/lib/sisimai/reason/systemerror.rb +7 -9
  112. data/lib/sisimai/reason/systemfull.rb +7 -8
  113. data/lib/sisimai/reason/toomanyconn.rb +9 -11
  114. data/lib/sisimai/reason/undefined.rb +2 -3
  115. data/lib/sisimai/reason/userunknown.rb +129 -146
  116. data/lib/sisimai/reason/vacation.rb +3 -4
  117. data/lib/sisimai/reason/virusdetected.rb +10 -11
  118. data/lib/sisimai/reason.rb +59 -64
  119. data/lib/sisimai/rfc1894.rb +55 -28
  120. data/lib/sisimai/rfc2045.rb +373 -0
  121. data/lib/sisimai/rfc3464.rb +250 -308
  122. data/lib/sisimai/rfc3834.rb +42 -45
  123. data/lib/sisimai/rfc5322.rb +75 -100
  124. data/lib/sisimai/rfc5965.rb +31 -0
  125. data/lib/sisimai/rhost/cox.rb +5 -6
  126. data/lib/sisimai/rhost/franceptt.rb +6 -8
  127. data/lib/sisimai/rhost/godaddy.rb +12 -12
  128. data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
  129. data/lib/sisimai/rhost/iua.rb +9 -10
  130. data/lib/sisimai/rhost/kddi.rb +6 -8
  131. data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
  132. data/lib/sisimai/rhost/mimecast.rb +42 -40
  133. data/lib/sisimai/rhost/nttdocomo.rb +12 -12
  134. data/lib/sisimai/rhost/spectrum.rb +10 -12
  135. data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
  136. data/lib/sisimai/rhost.rb +23 -31
  137. data/lib/sisimai/smtp/command.rb +59 -0
  138. data/lib/sisimai/smtp/error.rb +4 -7
  139. data/lib/sisimai/smtp/reply.rb +161 -74
  140. data/lib/sisimai/smtp/status.rb +504 -393
  141. data/lib/sisimai/smtp/transcript.rb +124 -0
  142. data/lib/sisimai/smtp.rb +0 -1
  143. data/lib/sisimai/string.rb +74 -5
  144. data/lib/sisimai/time.rb +1 -2
  145. data/lib/sisimai/version.rb +1 -1
  146. data/lib/sisimai.rb +35 -21
  147. data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
  148. data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
  149. data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
  150. data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
  151. data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
  152. data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
  153. data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
  154. data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
  155. data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
  156. data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
  157. data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
  158. data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
  159. data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
  160. data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
  161. data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
  162. data/sisimai-java.gemspec +1 -1
  163. data/sisimai.gemspec +1 -1
  164. metadata +42 -22
  165. data/.rspec +0 -2
  166. data/lib/sisimai/data/yaml.rb +0 -33
  167. data/lib/sisimai/data.rb +0 -411
  168. data/lib/sisimai/mime.rb +0 -456
  169. /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  170. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -1,7 +1,6 @@
1
1
  module Sisimai
2
2
  # Sisimai::DateTime provide methods for dealing date and time.
3
3
  module DateTime
4
- # Imported from p5-Sisimail/lib/Sisimai/DateTime.pm
5
4
  require 'date'
6
5
 
7
6
  class << self
@@ -172,40 +171,6 @@ module Sisimai
172
171
  #'YEKT' => '+0500', # Yekaterinburg Time UTC+05:00
173
172
  }.freeze
174
173
 
175
- # Convert to second
176
- # @param [String] argv1 Digit and a unit of time
177
- # @return [Integer] n: seconds
178
- # 0: 0 or invalid unit of time
179
- # @example Get the value of seconds
180
- # to_second('1d') #=> 86400
181
- # to_second('2h') #=> 7200
182
- def to_second(argv1)
183
- return 0 unless argv1.is_a?(::String)
184
-
185
- getseconds = 0
186
- unitoftime = TimeUnit.keys.join
187
- mathconsts = MathematicalConstant.keys.join
188
-
189
- if cr = argv1.match(/\A(\d+|\d+[.]\d+)([#{unitoftime}])?\z/)
190
- # 1d, 1.5w
191
- n = cr[1].to_f
192
- u = cr[2] || 'd'
193
- getseconds = n * TimeUnit[u].to_f
194
-
195
- elsif cr = argv1.match(/\A(\d+|\d+[.]\d+)?([#{mathconsts}])([#{unitoftime}])?\z/)
196
- # 1pd, 1.5pw
197
- n = cr[1].to_f || 1
198
- n = 1 if n.to_i == 0
199
- m = MathematicalConstant[cr[2]].to_f
200
- u = cr[3] || 'd'
201
- getseconds = n * m * TimeUnit[u].to_f
202
- else
203
- getseconds = 0
204
- end
205
-
206
- return getseconds
207
- end
208
-
209
174
  # Month name list
210
175
  # @param [Boolean] argv1 Require full name or not
211
176
  # @return [Array, String] Month name list or month name
@@ -217,17 +182,6 @@ module Sisimai
217
182
  return MonthName[value]
218
183
  end
219
184
 
220
- # List of day of week
221
- # @param [Boolean] argv1 Require full name
222
- # @return [Array, String] List of day of week or day of week
223
- # @example Get the names of each day of week
224
- # dayofweek() #=> [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
225
- # dayofweek(true) #=> [ 'Sunday', 'Monday', 'Tuesday', ... ]
226
- def dayofweek(argv1 = false)
227
- value = argv1 ? :full : :abbr
228
- return DayOfWeek[value]
229
- end
230
-
231
185
  # Parse date string; strptime() wrapper
232
186
  # @param [String] argv1 Date string
233
187
  # @return [String] Converted date string
@@ -259,7 +213,7 @@ module Sisimai
259
213
  # Parse each piece of time
260
214
  if p =~ /\A[A-Z][a-z]{2,}[,]?\z/
261
215
  # Day of week or Day of week; Thu, Apr, ...
262
- p.gsub!(/,\z/, '') if p.end_with?(',') # "Thu," => "Thu"
216
+ p[-1, 1] = '' if p.end_with?(',') # "Thu," => "Thu"
263
217
  p = p[0,3] if p.size > 3
264
218
 
265
219
  if DayOfWeek[:abbr].include?(p)
@@ -302,7 +256,7 @@ module Sisimai
302
256
  # Time: 1:4 => 01:04:00
303
257
  v[:T] = sprintf('%02d:%02d:00', cr[1].to_i, cr[2].to_i)
304
258
 
305
- elsif p =~ /\A[APap][Mm]\z/
259
+ elsif p.downcase == 'am' || p.downcase == 'pm'
306
260
  # AM or PM
307
261
  afternoon1 = 1
308
262
  else
@@ -394,8 +348,8 @@ module Sisimai
394
348
 
395
349
  # Abbreviation -> Tiemzone
396
350
  # @param [String] argv1 Abbr. e.g.) JST, GMT, PDT
397
- # @return [String, Nil] +0900, +0000, -0600 or nil if the argument is
398
- # invalid format or not supported abbreviation
351
+ # @return [String, Nil] +0900, +0000, -0600 or nil if the argument is invalid format or
352
+ # not supported abbreviation
399
353
  # @example Get the timezone string of "JST"
400
354
  # abbr2tz('JST') #=> '+0900'
401
355
  def abbr2tz(argv1)
@@ -405,8 +359,7 @@ module Sisimai
405
359
 
406
360
  # Convert to second
407
361
  # @param [String] argv1 Timezone string e.g) +0900
408
- # @return [Integer, Nil] n: seconds or nil it the argument is invalid
409
- # format string
362
+ # @return [Integer, Nil] n: seconds or nil it the argument is invalid format string
410
363
  # @see second2tz
411
364
  # @example Convert '+0900' to seconds
412
365
  # tz2second('+0900') #=> 32400
@@ -1,17 +1,15 @@
1
1
  module Sisimai
2
- class Data
3
- # Sisimai::Data::JSON dumps parsed data object as a JSON format. This class
4
- # and method should be called from the parent object "Sisimai::Data".
2
+ class Fact
3
+ # Sisimai::Fact::JSON dumps parsed data object as a JSON format. This class and method should be
4
+ # called from the parent object "Sisimai::Fact".
5
5
  module JSON
6
- # Imported from p5-Sisimail/lib/Sisimai/Data/JSON.pm
7
6
  class << self
8
- # Data dumper(JSON)
9
- # @param [Sisimai::Data] argvs Object
10
- # @return [String, Nil] Dumped data or nil if the argument
11
- # is missing
7
+ # Serializer (JSON)
8
+ # @param [Sisimai::Fact] argvs Object
9
+ # @return [String, nil] Dumped data or nil if the argument is missing
12
10
  def dump(argvs)
13
11
  return nil unless argvs
14
- return nil unless argvs.is_a? Sisimai::Data
12
+ return nil unless argvs.is_a? Sisimai::Fact
15
13
 
16
14
  if RUBY_PLATFORM.start_with?('java')
17
15
  # java-based ruby environment like JRuby.
@@ -0,0 +1,31 @@
1
+ module Sisimai
2
+ class Fact
3
+ # Sisimai::Fact::YAML dumps parsed data object as a YAML format. This class and method should be
4
+ # called from the parent object "Sisimai::Fact".
5
+ module YAML
6
+ class << self
7
+ require 'yaml'
8
+
9
+ # Serializer (YAML)
10
+ # @param [Sisimai::Fact] argvs Object
11
+ # @return [String, nil] Dumped data or nil if the argument is missing
12
+ def dump(argvs)
13
+ return nil unless argvs
14
+ return nil unless argvs.is_a? Sisimai::Fact
15
+
16
+ damneddata = argvs.damn
17
+ yamlstring = nil
18
+
19
+ begin
20
+ yamlstring = ::YAML.dump(damneddata)
21
+ rescue StandardError => ce
22
+ warn '***warning: Failed to YAML.dump: ' << ce.to_s
23
+ end
24
+
25
+ return yamlstring
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,468 @@
1
+ module Sisimai
2
+ # Sisimai::Fact generate parsed data
3
+ class Fact
4
+ require 'sisimai/message'
5
+ require 'sisimai/rfc1894'
6
+ require 'sisimai/rfc5322'
7
+ require 'sisimai/reason'
8
+ require 'sisimai/address'
9
+ require 'sisimai/datetime'
10
+ require 'sisimai/time'
11
+ require 'sisimai/smtp/error'
12
+ require 'sisimai/smtp/command'
13
+ require 'sisimai/string'
14
+ require 'sisimai/rhost'
15
+
16
+ @@rwaccessors = [
17
+ :action, # [String] The value of Action: header
18
+ :addresser, # [Sisimai::Address] From address
19
+ :alias, # [String] Alias of the recipient address
20
+ :catch, # [?] Results generated by hook method
21
+ :deliverystatus, # [String] Delivery Status(DSN)
22
+ :destination, # [String] The domain part of the "recipinet"
23
+ :diagnosticcode, # [String] Diagnostic-Code: Header
24
+ :diagnostictype, # [String] The 1st part of Diagnostic-Code: Header
25
+ :feedbacktype, # [String] Feedback Type
26
+ :hardbounce, # [Boolean] true = Hard bounce, false = is not a hard bounce
27
+ :lhost, # [String] local host name/Local MTA
28
+ :listid, # [String] List-Id header of each ML
29
+ :messageid, # [String] Message-Id: header
30
+ :origin, # [String] Email path as a data source
31
+ :reason, # [String] Bounce reason
32
+ :recipient, # [Sisimai::Address] Recipient address which bounced
33
+ :replycode, # [String] SMTP Reply Code
34
+ :rhost, # [String] Remote host name/Remote MTA
35
+ :senderdomain, # [String] The domain part of the "addresser"
36
+ :smtpagent, # [String] Module(Engine) name
37
+ :smtpcommand, # [String] The last SMTP command
38
+ :subject, # [String] UTF-8 Subject text
39
+ :timestamp, # [Sisimai::Time] Date: header in the original message
40
+ :timezoneoffset, # [Integer] Time zone offset(seconds)
41
+ :token, # [String] Message token/MD5 Hex digest value
42
+ ]
43
+ attr_accessor(*@@rwaccessors)
44
+
45
+ RetryIndex = Sisimai::Reason.retry
46
+ RFC822Head = Sisimai::RFC5322.HEADERFIELDS(:all)
47
+ ActionList = { delayed: 1, delivered: 1, expanded: 1, failed: 1, relayed: 1 };
48
+
49
+ # Constructor of Sisimai::Fact
50
+ # @param [Hash] argvs Including each parameter
51
+ # @return [Sisimai::Fact] Structured email data
52
+ def initialize(argvs)
53
+ # Create email address object
54
+ @alias = argvs['alias'] || ''
55
+ @addresser = argvs['addresser']
56
+ @action = argvs['action']
57
+ @catch = argvs['catch']
58
+ @diagnosticcode = argvs['diagnosticcode']
59
+ @diagnostictype = argvs['diagnostictype']
60
+ @deliverystatus = argvs['deliverystatus']
61
+ @destination = argvs['recipient'].host
62
+ @feedbacktype = argvs['feedbacktype']
63
+ @hardbounce = argvs['hardbounce']
64
+ @lhost = argvs['lhost']
65
+ @listid = argvs['listid']
66
+ @messageid = argvs['messageid']
67
+ @origin = argvs['origin']
68
+ @reason = argvs['reason']
69
+ @recipient = argvs['recipient']
70
+ @replycode = argvs['replycode']
71
+ @rhost = argvs['rhost']
72
+ @senderdomain = argvs['addresser'].host
73
+ @smtpagent = argvs['smtpagent']
74
+ @smtpcommand = argvs['smtpcommand']
75
+ @subject = argvs['subject']
76
+ @token = argvs['token']
77
+ @timestamp = argvs['timestamp']
78
+ @timezoneoffset = argvs['timezoneoffset']
79
+ end
80
+
81
+ # Constructor of Sisimai::Fact
82
+ # @param [Hash] argvs
83
+ # @options argvs [String] data Entire email message
84
+ # @options argvs [Boolean] delivered Include the result which has "delivered" reason
85
+ # @options argvs [Boolean] vacation Include the result which has "vacation" reason
86
+ # @options argvs [Proc] hook Proc object of callback method
87
+ # @options argvs [Array] load User defined MTA module list
88
+ # @options argvs [Array] order The order of MTA modules
89
+ # @options argvs [String] origin Path to the original email file
90
+ # @return [Array] Array of Sisimai::Fact objects
91
+ def self.rise(**argvs)
92
+ return nil unless argvs
93
+ return nil unless argvs.is_a? Hash
94
+
95
+ email = argvs[:data]; return nil unless email
96
+ loads = argvs[:load] || nil
97
+ order = argvs[:order] || nil
98
+ args1 = { data: email, hook: argvs[:hook], load: loads, order: order }
99
+ mesg1 = Sisimai::Message.rise(**args1)
100
+
101
+ return nil unless mesg1
102
+ return nil unless mesg1['ds']
103
+ return nil unless mesg1['rfc822']
104
+
105
+ deliveries = mesg1['ds'].dup
106
+ rfc822data = mesg1['rfc822']
107
+ listoffact = [];
108
+
109
+ while e = deliveries.shift do
110
+ # Create parameters for each Sisimai::Fact object
111
+ o = {} # To be passed to each accessor of Sisimai::Fact
112
+ p = {
113
+ 'action' => e['action'] || '',
114
+ 'alias' => e['alias'] || '',
115
+ 'catch' => mesg1['catch'] || nil,
116
+ 'deliverystatus' => e['status'] || '',
117
+ 'diagnosticcode' => e['diagnosis'] || '',
118
+ 'diagnostictype' => e['spec'] || '',
119
+ 'feedbacktype' => e['feedbacktype'] || '',
120
+ 'hardbounce' => false,
121
+ 'lhost' => e['lhost'] || '',
122
+ 'origin' => argvs[:origin],
123
+ 'reason' => e['reason'] || '',
124
+ 'recipient' => e['recipient'] || '',
125
+ 'replycode' => e['replycode'] || '',
126
+ 'rhost' => e['rhost'] || '',
127
+ 'smtpagent' => e['agent'] || '',
128
+ 'smtpcommand' => e['command'] || '',
129
+ }
130
+ unless argvs[:delivered]
131
+ # Skip if the value of "deliverystatus" begins with "2." such as 2.1.5
132
+ next if p['deliverystatus'].start_with?('2.')
133
+ end
134
+
135
+ unless argvs[:vacation]
136
+ # Skip if the value of "reason" is "vacation"
137
+ next if p['reason'] == 'vacation'
138
+ end
139
+
140
+ # EMAILADDRESS: Detect email address from message/rfc822 part
141
+ RFC822Head[:addresser].each do |f|
142
+ # Check each header in message/rfc822 part
143
+ g = f.downcase
144
+ next unless rfc822data[g]
145
+ next if rfc822data[g].empty?
146
+
147
+ j = Sisimai::Address.find(rfc822data[g]) || next
148
+ p['addresser'] = j.shift
149
+ break
150
+ end
151
+
152
+ unless p['addresser']
153
+ # Fallback: Get the sender address from the header of the bounced email if the address is
154
+ # not set at loop above.
155
+ j = Sisimai::Address.find(mesg1['header']['to']) || []
156
+ p['addresser'] = j.shift
157
+ end
158
+ next unless p['addresser']
159
+ next unless p['recipient']
160
+
161
+ # TIMESTAMP: Convert from a time stamp or a date string to a machine time.
162
+ datestring = nil
163
+ zoneoffset = 0
164
+ datevalues = []; datevalues << e['date'] unless e['date'].to_s.empty?
165
+
166
+ # Date information did not exist in message/delivery-status part,...
167
+ RFC822Head[:date].each do |f|
168
+ # Get the value of Date header or other date related header.
169
+ next unless rfc822data[f]
170
+ datevalues << rfc822data[f]
171
+ end
172
+
173
+ # Set "date" getting from the value of "Date" in the bounce message
174
+ datevalues << mesg1['header']['date'] if datevalues.size < 2
175
+
176
+ while v = datevalues.shift do
177
+ # Parse each date value in the array
178
+ datestring = Sisimai::DateTime.parse(v)
179
+ break if datestring
180
+ end
181
+
182
+ if datestring && cv = datestring.match(/\A(.+)[ ]+([-+]\d{4})\z/)
183
+ # Get the value of timezone offset from datestring: Wed, 26 Feb 2014 06:05:48 -0500
184
+ datestring = cv[1]
185
+ zoneoffset = Sisimai::DateTime.tz2second(cv[2])
186
+ p['timezoneoffset'] = cv[2]
187
+ end
188
+
189
+ begin
190
+ # Convert from the date string to an object then calculate time zone offset.
191
+ t = Sisimai::Time.strptime(datestring, '%a, %d %b %Y %T')
192
+ p['timestamp'] = (t.to_time.to_i - zoneoffset) || nil
193
+ rescue
194
+ warn ' ***warning: Failed to strptime ' << datestring.to_s
195
+ end
196
+ next unless p['timestamp']
197
+
198
+ # OTHER_TEXT_HEADERS:
199
+ recvheader = mesg1['header']['received'] || []
200
+ unless recvheader.empty?
201
+ # Get localhost and remote host name from Received header.
202
+ %w[lhost rhost].each { |a| e[a] ||= '' }
203
+ e['lhost'] = Sisimai::RFC5322.received(recvheader[0]).shift if e['lhost'].empty?
204
+ e['rhost'] = Sisimai::RFC5322.received(recvheader[-1]).pop if e['rhost'].empty?
205
+ end
206
+
207
+ # Remove square brackets and curly brackets from the host variable
208
+ %w[rhost lhost].each do |v|
209
+ p[v] = p[v].split('@')[-1] if p[v].include?('@')
210
+ p[v].delete!('[]()') # Remove square brackets and curly brackets from the host variable
211
+ p[v].sub!(/\A.+=/, '') # Remove string before "="
212
+ p[v].chomp!("\r") if p[v].end_with?("\r") # Remove CR at the end of the value
213
+
214
+ # Check space character in each value and get the first element
215
+ p[v] = p[v].split(' ', 2).shift if p[v].include?(' ')
216
+ p[v].chomp!('.') if p[v].end_with?('.') # Remove "." at the end of the value
217
+ end
218
+
219
+ # Subject: header of the original message
220
+ p['subject'] = rfc822data['subject'] || ''
221
+ p['subject'].scrub!('?')
222
+ p['subject'].chomp!("\r") if p['subject'].end_with?("\r")
223
+
224
+ # The value of "List-Id" header
225
+ if Sisimai::String.aligned(rfc822data['list-id'], ['<', '.', '>'])
226
+ # https://www.rfc-editor.org/rfc/rfc2919
227
+ # Get the value of List-Id header: "List name <list-id@example.org>"
228
+ p0 = rfc822data['list-id'].index('<') + 1
229
+ p1 = rfc822data['list-id'].index('>')
230
+ p['listid'] = rfc822data['list-id'][p0, p1 - p0]
231
+ else
232
+ # Invalid value of the List-Id: field
233
+ p['listid'] = ''
234
+ end
235
+
236
+ # The value of "Message-Id" header
237
+ if Sisimai::String.aligned(rfc822data['message-id'], ['<', '@', '>'])
238
+ # https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4
239
+ # Leave only string inside of angle brackets(<>)
240
+ p0 = rfc822data['message-id'].index('<') + 1
241
+ p1 = rfc822data['message-id'].index('>')
242
+ p['messageid'] = rfc822data['message-id'][p0, p1 - p0]
243
+ else
244
+ # Invalid value of the Message-Id: field
245
+ p['messageid'] = ''
246
+ end
247
+
248
+ # CHECK_DELIVERY_STATUS_VALUE: Cleanup the value of "Diagnostic-Code:" header
249
+ if p['diagnosticcode'].to_s.size > 0
250
+ # Get an SMTP Reply Code and an SMTP Enhanced Status Code
251
+ p['diagnosticcode'].chop if p['diagnosticcode'][-1, 1] == "\r"
252
+
253
+ cs = Sisimai::SMTP::Status.find(p['diagnosticcode']) || ''
254
+ cr = Sisimai::SMTP::Reply.find(p['diagnosticcode'], cs) || ''
255
+ p['deliverystatus'] = Sisimai::SMTP::Status.prefer(p['deliverystatus'], cs, cr)
256
+
257
+ if cr.size == 3
258
+ # There is an SMTP reply code in the error message
259
+ p['replycode'] = cr if p['replycode'].to_s.empty?
260
+
261
+ if p['diagnosticcode'].include?(cr + '-')
262
+ # 550-5.7.1 [192.0.2.222] Our system has detected that this message is
263
+ # 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail,
264
+ # 550-5.7.1 this message has been blocked. Please visit
265
+ # 550 5.7.1 https://support.google.com/mail/answer/188131 for more information.
266
+ #
267
+ # kijitora@example.co.uk
268
+ # host c.eu.example.com [192.0.2.3]
269
+ # SMTP error from remote mail server after end of data:
270
+ # 553-SPF (Sender Policy Framework) domain authentication
271
+ # 553-fail. Refer to the Troubleshooting page at
272
+ # 553-http://www.symanteccloud.com/troubleshooting for more
273
+ # 553 information. (#5.7.1)
274
+ ['-', " "].each do |q|
275
+ # Remove strings: "550-5.7.1", and "550 5.7.1" from the error message
276
+ cx = sprintf("%s%s%s", cr, q, cs)
277
+ p0 = p['diagnosticcode'].index(cx)
278
+ while p0
279
+ # Remove strings like "550-5.7.1"
280
+ p['diagnosticcode'][p0, cx.size] = ''
281
+ p0 = p['diagnosticcode'].index(cx)
282
+ end
283
+
284
+ # Remove "553-" and "553 " (SMTP reply code only) from the error message
285
+ cx = sprintf("%s%s", cr, q)
286
+ p0 = p['diagnosticcode'].index(cx)
287
+ while p0
288
+ # Remove strings like "553-"
289
+ p['diagnosticcode'][p0, cx.size] = ''
290
+ p0 = p['diagnosticcode'].index(cx)
291
+ end
292
+ end
293
+
294
+ if p['diagnosticcode'].index(cr).to_i > 1
295
+ # Add "550 5.1.1" into the head of the error message when the error message does not
296
+ # begin with "550"
297
+ p['diagnosticcode'] = sprintf("%s %s %s", cr, cs, p['diagnosticcode'])
298
+ end
299
+ end
300
+ end
301
+
302
+ p1 = p['diagnosticcode'].downcase.index('<html>')
303
+ p2 = p['diagnosticcode'].downcase.index('</html>')
304
+ p['diagnosticcode'][p1, p2 + 7 - p1] = '' if p1 && p2
305
+ p['diagnosticcode'] = Sisimai::String.sweep(p['diagnosticcode'])
306
+ end
307
+
308
+ if Sisimai::String.is_8bit(p['diagnosticcode'])
309
+ # To avoid incompatible character encodings: ASCII-8BIT and UTF-8 (Encoding::CompatibilityError
310
+ p['diagnosticcode'] = p['diagnosticcode'].force_encoding('UTF-8').scrub('?')
311
+ end
312
+
313
+ p['diagnostictype'] = nil if p['diagnostictype'].empty?
314
+ p['diagnostictype'] ||= 'X-UNIX' if p['reason'] == 'mailererror'
315
+ p['diagnostictype'] ||= 'SMTP' unless %w[feedback vacation].include?(p['reason'])
316
+
317
+ # Check the value of SMTP command
318
+ p['smtpcommand'] = '' unless Sisimai::SMTP::Command.test(p['smtpcommand'])
319
+
320
+ # Create parameters for the constructor
321
+ as = Sisimai::Address.new(p['addresser']) || next; next if as.void
322
+ ar = Sisimai::Address.new(address: p['recipient']) || next; next if ar.void
323
+ ea = %w[
324
+ action deliverystatus diagnosticcode diagnostictype feedbacktype lhost listid messageid
325
+ origin reason replycode rhost smtpagent smtpcommand subject
326
+ ]
327
+
328
+ o = {
329
+ 'addresser' => as,
330
+ 'recipient' => ar,
331
+ 'senderdomain' => as.host,
332
+ 'destination' => ar.host,
333
+ 'alias' => p['alias'] || ar.alias,
334
+ 'token' => Sisimai::String.token(as.address, ar.address, p['timestamp']),
335
+ }
336
+
337
+ # Other accessors
338
+ ea.each { |q| o[q] ||= p[q] || '' }
339
+ o['catch'] = p['catch'] || nil
340
+ o['hardbounce'] = p['hardbounce']
341
+ o['replycode'] = Sisimai::SMTP::Reply.find(p['diagnosticcode']).to_s if o['replycode'].empty?
342
+ o['timestamp'] = Sisimai::Time.parse(::Time.at(p['timestamp']).to_s)
343
+ o['timezoneoffset'] = p['timezoneoffset'] || '+0000'
344
+
345
+ # REASON: Decide the reason of email bounce
346
+ if o['reason'].empty? || RetryIndex[o['reason']]
347
+ # The value of "reason" is empty or is needed to check with other values again
348
+ re = ''; de = o['destination']
349
+ re = Sisimai::Rhost.get(o) if Sisimai::Rhost.match(o['rhost'])
350
+ if re.empty?
351
+ # Failed to detect a bounce reason by the value of "rhost"
352
+ re = Sisimai::Rhost.get(o, de) if Sisimai::Rhost.match(de)
353
+ re = Sisimai::Reason.get(o) if re.empty?
354
+ re = 'undefined' if re.empty?
355
+ end
356
+ o['reason'] = re
357
+ end
358
+
359
+ # HARDBOUNCE: Set the value of "hardbounce", default value of "bouncebounce" is false
360
+ if o['reason'] == 'delivered' || o['reason'] == 'feedback' || o['reason'] == 'vacation'
361
+ # The value of "reason" is "delivered", "vacation" or "feedback".
362
+ o['replycode'] = '' unless o['reason'] == 'delivered'
363
+ else
364
+ smtperrors = p['deliverystatus'] + ' ' << p['diagnosticcode']
365
+ smtperrors = '' if smtperrors.size < 4
366
+ softorhard = Sisimai::SMTP::Error.soft_or_hard(o['reason'], smtperrors)
367
+ o['hardbounce'] = true if softorhard == 'hard'
368
+ end
369
+
370
+ # DELIVERYSTATUS: Set a pseudo status code if the value of "deliverystatus" is empty
371
+ if o['deliverystatus'].empty?
372
+ smtperrors = p['replycode'] + ' ' << p['diagnosticcode']
373
+ smtperrors = '' if smtperrors.size < 4
374
+ permanent1 = Sisimai::SMTP::Error.is_permanent(smtperrors)
375
+ permanent1 = true if permanent1 == nil
376
+ o['deliverystatus'] = Sisimai::SMTP::Status.code(o['reason'], permanent1 ? false : true) || ''
377
+ end
378
+
379
+ # REPLYCODE: Check both of the first digit of "deliverystatus" and "replycode"
380
+ cx = [o['deliverystatus'][0, 1], o['replycode'][0, 1]]
381
+ if cx[0] != cx[1]
382
+ # The class of the "Status:" is defer with the first digit of the reply code
383
+ cx[1] = Sisimai::SMTP::Reply.find(p['diagnosticcode'], cx[0]) || ''
384
+ o['replycode'] = cx[1].start_with?(cx[0]) ? cx[1] : ''
385
+ end
386
+
387
+ unless ActionList.has_key?(o['action'])
388
+ # There is an action value which is not described at RFC1894
389
+ if ox = Sisimai::RFC1894.field('Action: ' << o['action'])
390
+ # Rewrite the value of "Action:" field to the valid value
391
+ #
392
+ # The syntax for the action-field is:
393
+ # action-field = "Action" ":" action-value
394
+ # action-value = "failed" / "delayed" / "delivered" / "relayed" / "expanded"
395
+ o['action'] = ox[2]
396
+ end
397
+ end
398
+ o['action'] = 'delayed' if o['reason'] == 'expired'
399
+ if o['action'].empty?
400
+ o['action'] = 'failed' if cx[0] == '4' || cx[0] == '5'
401
+ end
402
+
403
+ listoffact << Sisimai::Fact.new(o)
404
+ end
405
+ return listoffact
406
+ end
407
+
408
+ # Emulate "softbounce" accessor for the backward compatible
409
+ # @return [Integer]
410
+ def softbounce
411
+ warn ' ***warning: Sisimai::Fact.softbounce will be removed at v5.1.0. Use Sisimai::Fact.hardbounce instead'
412
+ return 0 if self.hardbounce
413
+ return -1 if self.reason == 'delivered' || self.reason == 'feedback' || self.reason == 'vacation'
414
+ return 1
415
+ end
416
+
417
+ # Convert from Sisimai::Fact object to a Hash
418
+ # @return [Hash] Hashed data
419
+ def damn
420
+ data = {}
421
+ stringdata = %w[
422
+ action alias catch deliverystatus destination diagnosticcode diagnostictype feedbacktype
423
+ lhost listid messageid origin reason replycode rhost senderdomain smtpagent smtpcommand
424
+ subject timezoneoffset token
425
+ ]
426
+
427
+ begin
428
+ v = {}
429
+ stringdata.each { |e| v[e] = self.send(e.to_sym) || '' }
430
+ v['hardbounce'] = self.hardbounce
431
+ v['addresser'] = self.addresser.address
432
+ v['recipient'] = self.recipient.address
433
+ v['timestamp'] = self.timestamp.to_time.to_i
434
+ data = v
435
+ rescue
436
+ warn ' ***warning: Failed to execute Sisimai::Fact.damn'
437
+ end
438
+ return data
439
+ end
440
+ alias :to_hash :damn
441
+
442
+ # Data dumper
443
+ # @param [String] type Data format: json, yaml
444
+ # @return [String] data
445
+ # [Nil] The value of the first argument is neither "json" nor "yaml"
446
+ def dump(type = 'json')
447
+ return nil unless %w[json yaml].include?(type)
448
+ referclass = 'Sisimai::Fact::' << type.upcase
449
+
450
+ begin
451
+ require referclass.downcase.gsub('::', '/')
452
+ rescue
453
+ warn '***warning: Failed to load' << referclass
454
+ end
455
+
456
+ dumpeddata = Module.const_get(referclass).dump(self)
457
+ return dumpeddata
458
+ end
459
+
460
+ # JSON handler
461
+ # @return [String] JSON string converted from Sisimai::Fact
462
+ def to_json(*)
463
+ return self.dump('json')
464
+ end
465
+
466
+ end
467
+ end
468
+