sisimai 4.25.17 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +406 -407
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +12 -12
- data/README-JA.md +142 -94
- data/README.md +282 -150
- data/Rakefile +9 -3
- data/Repository.mk +2 -3
- data/lib/sisimai/address.rb +118 -74
- data/lib/sisimai/arf.rb +84 -82
- data/lib/sisimai/datetime.rb +5 -52
- data/lib/sisimai/{data → fact}/json.rb +7 -9
- data/lib/sisimai/fact/yaml.rb +31 -0
- data/lib/sisimai/fact.rb +468 -0
- data/lib/sisimai/lhost/activehunter.rb +12 -14
- data/lib/sisimai/lhost/amavis.rb +11 -14
- data/lib/sisimai/lhost/amazonses.rb +37 -41
- data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
- data/lib/sisimai/lhost/aol.rb +12 -14
- data/lib/sisimai/lhost/apachejames.rb +19 -21
- data/lib/sisimai/lhost/barracuda.rb +10 -12
- data/lib/sisimai/lhost/bigfoot.rb +21 -21
- data/lib/sisimai/lhost/biglobe.rb +15 -16
- data/lib/sisimai/lhost/courier.rb +20 -20
- data/lib/sisimai/lhost/domino.rb +23 -19
- data/lib/sisimai/lhost/einsundeins.rb +20 -16
- data/lib/sisimai/lhost/exchange2003.rb +30 -29
- data/lib/sisimai/lhost/exchange2007.rb +70 -58
- data/lib/sisimai/lhost/exim.rb +175 -161
- data/lib/sisimai/lhost/ezweb.rb +31 -56
- data/lib/sisimai/lhost/facebook.rb +21 -33
- data/lib/sisimai/lhost/fml.rb +43 -48
- data/lib/sisimai/lhost/gmail.rb +29 -29
- data/lib/sisimai/lhost/gmx.rb +18 -17
- data/lib/sisimai/lhost/googlegroups.rb +9 -10
- data/lib/sisimai/lhost/gsuite.rb +21 -27
- data/lib/sisimai/lhost/imailserver.rb +25 -39
- data/lib/sisimai/lhost/interscanmss.rb +28 -31
- data/lib/sisimai/lhost/kddi.rb +22 -28
- data/lib/sisimai/lhost/mailfoundry.rb +11 -12
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
- data/lib/sisimai/lhost/mailru.rb +33 -27
- data/lib/sisimai/lhost/mcafee.rb +21 -31
- data/lib/sisimai/lhost/messagelabs.rb +17 -20
- data/lib/sisimai/lhost/messagingserver.rb +40 -37
- data/lib/sisimai/lhost/mfilter.rb +15 -16
- data/lib/sisimai/lhost/mxlogic.rb +24 -23
- data/lib/sisimai/lhost/notes.rb +17 -17
- data/lib/sisimai/lhost/office365.rb +63 -27
- data/lib/sisimai/lhost/opensmtpd.rb +12 -13
- data/lib/sisimai/lhost/outlook.rb +12 -15
- data/lib/sisimai/lhost/postfix.rb +179 -129
- data/lib/sisimai/lhost/powermta.rb +12 -14
- data/lib/sisimai/lhost/qmail.rb +44 -47
- data/lib/sisimai/lhost/receivingses.rb +15 -20
- data/lib/sisimai/lhost/sendgrid.rb +34 -32
- data/lib/sisimai/lhost/sendmail.rb +66 -53
- data/lib/sisimai/lhost/surfcontrol.rb +19 -19
- data/lib/sisimai/lhost/v5sendmail.rb +45 -39
- data/lib/sisimai/lhost/verizon.rb +35 -39
- data/lib/sisimai/lhost/x1.rb +18 -17
- data/lib/sisimai/lhost/x2.rb +17 -14
- data/lib/sisimai/lhost/x3.rb +19 -19
- data/lib/sisimai/lhost/x4.rb +72 -57
- data/lib/sisimai/lhost/x5.rb +17 -19
- data/lib/sisimai/lhost/x6.rb +41 -17
- data/lib/sisimai/lhost/yahoo.rb +17 -16
- data/lib/sisimai/lhost/yandex.rb +16 -20
- data/lib/sisimai/lhost/zoho.rb +16 -15
- data/lib/sisimai/lhost.rb +8 -10
- data/lib/sisimai/mail/maildir.rb +1 -3
- data/lib/sisimai/mail/mbox.rb +3 -4
- data/lib/sisimai/mail/memory.rb +0 -1
- data/lib/sisimai/mail/stdin.rb +1 -3
- data/lib/sisimai/mail.rb +3 -7
- data/lib/sisimai/mda.rb +28 -42
- data/lib/sisimai/message.rb +435 -325
- data/lib/sisimai/order.rb +5 -5
- data/lib/sisimai/reason/authfailure.rb +64 -0
- data/lib/sisimai/reason/badreputation.rb +53 -0
- data/lib/sisimai/reason/blocked.rb +94 -160
- data/lib/sisimai/reason/contenterror.rb +8 -9
- data/lib/sisimai/reason/delivered.rb +4 -6
- data/lib/sisimai/reason/exceedlimit.rb +10 -12
- data/lib/sisimai/reason/expired.rb +6 -8
- data/lib/sisimai/reason/feedback.rb +2 -3
- data/lib/sisimai/reason/filtered.rb +17 -19
- data/lib/sisimai/reason/hasmoved.rb +9 -10
- data/lib/sisimai/reason/hostunknown.rb +15 -15
- data/lib/sisimai/reason/mailboxfull.rb +10 -12
- data/lib/sisimai/reason/mailererror.rb +18 -20
- data/lib/sisimai/reason/mesgtoobig.rb +9 -11
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +8 -11
- data/lib/sisimai/reason/notaccept.rb +13 -14
- data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +14 -12
- data/lib/sisimai/reason/rejected.rb +26 -24
- data/lib/sisimai/reason/requireptr.rb +69 -0
- data/lib/sisimai/reason/securityerror.rb +33 -36
- data/lib/sisimai/reason/spamdetected.rb +114 -147
- data/lib/sisimai/reason/speeding.rb +49 -0
- data/lib/sisimai/reason/suspend.rb +11 -11
- data/lib/sisimai/reason/syntaxerror.rb +11 -10
- data/lib/sisimai/reason/systemerror.rb +7 -9
- data/lib/sisimai/reason/systemfull.rb +7 -8
- data/lib/sisimai/reason/toomanyconn.rb +9 -11
- data/lib/sisimai/reason/undefined.rb +2 -3
- data/lib/sisimai/reason/userunknown.rb +129 -146
- data/lib/sisimai/reason/vacation.rb +3 -4
- data/lib/sisimai/reason/virusdetected.rb +10 -11
- data/lib/sisimai/reason.rb +59 -64
- data/lib/sisimai/rfc1894.rb +55 -28
- data/lib/sisimai/rfc2045.rb +373 -0
- data/lib/sisimai/rfc3464.rb +250 -308
- data/lib/sisimai/rfc3834.rb +42 -45
- data/lib/sisimai/rfc5322.rb +75 -100
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +42 -40
- data/lib/sisimai/rhost/nttdocomo.rb +13 -18
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +504 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +35 -21
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +41 -21
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- data/set-of-emails/maildir/mac/reported-from-nick4tech-san-01.eml +0 -6
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
data/lib/sisimai/datetime.rb
CHANGED
@@ -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
|
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
|
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
|
-
#
|
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
|
3
|
-
# Sisimai::
|
4
|
-
#
|
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
|
-
#
|
9
|
-
# @param [Sisimai::
|
10
|
-
# @return [String,
|
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::
|
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
|
data/lib/sisimai/fact.rb
ADDED
@@ -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
|
+
|