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.
- checksums.yaml +5 -5
- data/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +412 -393
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +15 -15
- data/README-JA.md +140 -78
- data/README.md +290 -143
- 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 +23 -18
- 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 -326
- 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 +12 -12
- 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 +42 -22
- 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/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
|
+
|