sisimai 4.25.3 → 4.25.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sisimai might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/ANALYTICAL-PRECISION +2 -4
- data/CONTRIBUTING +4 -0
- data/ChangeLog.md +39 -0
- data/Developers.mk +1 -72
- data/Makefile +4 -0
- data/README-JA.md +3 -0
- data/README.md +5 -2
- data/lib/sisimai.rb +4 -3
- data/lib/sisimai/arf.rb +6 -6
- data/lib/sisimai/bite.rb +8 -3
- data/lib/sisimai/bite/email.rb +7 -50
- data/lib/sisimai/bite/json.rb +5 -25
- data/lib/sisimai/data.rb +1 -1
- data/lib/sisimai/lhost.rb +101 -0
- data/lib/sisimai/{bite/email → lhost}/activehunter.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/amavis.rb +9 -9
- data/lib/sisimai/lhost/amazonses.rb +403 -0
- data/lib/sisimai/{bite/email → lhost}/amazonworkmail.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/aol.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/apachejames.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/bigfoot.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/biglobe.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/courier.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/domino.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/einsundeins.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/exchange2003.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/exchange2007.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/exim.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/ezweb.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/facebook.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/fml.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/gmx.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/google.rb +14 -14
- data/lib/sisimai/{bite/email → lhost}/gsuite.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/imailserver.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/interscanmss.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/kddi.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/mailfoundry.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/mailmarshalsmtp.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/mailru.rb +11 -11
- data/lib/sisimai/{bite/email → lhost}/mcafee.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/messagelabs.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/messagingserver.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/mfilter.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/mxlogic.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/notes.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/office365.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/opensmtpd.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/outlook.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/postfix.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/qmail.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/receivingses.rb +11 -11
- data/lib/sisimai/{bite/email → lhost}/sendgrid.rb +120 -11
- data/lib/sisimai/{bite/email → lhost}/sendmail.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/surfcontrol.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/userdefined.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/v5sendmail.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/verizon.rb +11 -11
- data/lib/sisimai/{bite/email → lhost}/x1.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/x2.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/x3.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/x4.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/x5.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/yahoo.rb +9 -9
- data/lib/sisimai/{bite/email → lhost}/yandex.rb +10 -10
- data/lib/sisimai/{bite/email → lhost}/zoho.rb +10 -10
- data/lib/sisimai/mda.rb +1 -1
- data/lib/sisimai/message.rb +535 -28
- data/lib/sisimai/message/email.rb +3 -454
- data/lib/sisimai/message/json.rb +3 -177
- data/lib/sisimai/order.rb +238 -4
- data/lib/sisimai/order/email.rb +5 -190
- data/lib/sisimai/order/json.rb +3 -39
- data/lib/sisimai/reason.rb +1 -1
- data/lib/sisimai/reason/norelaying.rb +1 -1
- data/lib/sisimai/reason/userunknown.rb +1 -1
- data/lib/sisimai/reason/virusdetected.rb +1 -0
- data/lib/sisimai/rfc1894.rb +2 -2
- data/lib/sisimai/rfc3464.rb +13 -13
- data/lib/sisimai/rfc3834.rb +3 -3
- data/lib/sisimai/rfc5322.rb +1 -1
- data/lib/sisimai/rhost.rb +5 -4
- data/lib/sisimai/rhost/exchangeonline.rb +1 -1
- data/lib/sisimai/rhost/franceptt.rb +1 -1
- data/lib/sisimai/rhost/godaddy.rb +1 -1
- data/lib/sisimai/rhost/iua.rb +39 -0
- data/lib/sisimai/version.rb +1 -1
- data/set-of-emails/README.md +0 -1
- data/set-of-emails/maildir/bsd/email-gsuite-13.eml +261 -0
- data/set-of-emails/maildir/bsd/email-postfix-62.eml +105 -0
- data/set-of-emails/maildir/bsd/email-postfix-63.eml +85 -0
- data/set-of-emails/maildir/bsd/rhost-iua-01.eml +70 -0
- metadata +62 -82
- data/fig/sisimai-architecture.png +0 -0
- data/lib/sisimai/bite/email/amazonses.rb +0 -186
- data/lib/sisimai/bite/json/amazonses.rb +0 -242
- data/lib/sisimai/bite/json/sendgrid.rb +0 -123
- data/set-of-emails/jsonobj/json-amazonses-01.json +0 -1
- data/set-of-emails/jsonobj/json-amazonses-02.json +0 -11
- data/set-of-emails/jsonobj/json-amazonses-03.json +0 -1
- data/set-of-emails/jsonobj/json-amazonses-04.json +0 -1
- data/set-of-emails/jsonobj/json-amazonses-05.json +0 -1
- data/set-of-emails/jsonobj/json-amazonses-06.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-01.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-02.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-03.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-04.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-05.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-06.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-07.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-08.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-09.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-10.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-11.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-12.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-13.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-14.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-15.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-16.json +0 -1
- data/set-of-emails/jsonobj/json-sendgrid-17.json +0 -1
@@ -1,19 +1,19 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::X2 parses a bounce email which created by Unknown
|
3
3
|
# MTA #2. Methods in the module are called from only Sisimai::Message.
|
4
4
|
module X2
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
7
|
-
require 'sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/X2.pm
|
7
|
+
require 'sisimai/lhost'
|
8
8
|
|
9
|
-
Indicators = Sisimai::
|
9
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
10
10
|
StartingOf = {
|
11
11
|
message: ['Unable to deliver message to the following address'],
|
12
12
|
rfc822: ['--- Original message follows'],
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
def description; return 'Unknown MTA #2'; end
|
16
|
-
def smtpagent; return Sisimai::
|
16
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
17
17
|
def headerlist; return []; end
|
18
18
|
|
19
19
|
# Parse bounce messages from Unknown MTA #2
|
@@ -27,11 +27,11 @@ module Sisimai::Bite::Email
|
|
27
27
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
28
28
|
# part or nil if it failed to parse or
|
29
29
|
# the arguments are missing
|
30
|
-
def
|
30
|
+
def make(mhead, mbody)
|
31
31
|
return nil unless mhead['from'].include?('MAILER-DAEMON@')
|
32
32
|
return nil unless mhead['subject'] =~ %r/\A(?>Delivery failure|fail(?:ure|ed) delivery)/
|
33
33
|
|
34
|
-
dscontents = [Sisimai::
|
34
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
35
35
|
hasdivided = mbody.split("\n")
|
36
36
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
37
37
|
blanklines = 0 # (Integer) The number of blank lines
|
@@ -80,7 +80,7 @@ module Sisimai::Bite::Email
|
|
80
80
|
# <kijitora@example.com>:
|
81
81
|
if v['recipient']
|
82
82
|
# There are multiple recipient addresses in the message body.
|
83
|
-
dscontents << Sisimai::
|
83
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
84
84
|
v = dscontents[-1]
|
85
85
|
end
|
86
86
|
v['recipient'] = cv[1]
|
@@ -1,19 +1,19 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::X3 parses a bounce email which created by Unknown
|
3
3
|
# MTA #3. Methods in the module are called from only Sisimai::Message.
|
4
4
|
module X3
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
7
|
-
require 'sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/X3.pm
|
7
|
+
require 'sisimai/lhost'
|
8
8
|
|
9
|
-
Indicators = Sisimai::
|
9
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
10
10
|
StartingOf = {
|
11
11
|
message: [' This is an automatically generated Delivery Status Notification.'],
|
12
12
|
rfc822: ['Content-Type: message/rfc822'],
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
def description; return 'Unknown MTA #3'; end
|
16
|
-
def smtpagent; return Sisimai::
|
16
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
17
17
|
def headerlist; return []; end
|
18
18
|
|
19
19
|
# Parse bounce messages from Unknown MTA #3
|
@@ -27,11 +27,11 @@ module Sisimai::Bite::Email
|
|
27
27
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
28
28
|
# part or nil if it failed to parse or
|
29
29
|
# the arguments are missing
|
30
|
-
def
|
30
|
+
def make(mhead, mbody)
|
31
31
|
return nil unless mhead['subject'].start_with?('Delivery status notification')
|
32
32
|
return nil unless mhead['from'].start_with?('Mail Delivery System')
|
33
33
|
|
34
|
-
dscontents = [Sisimai::
|
34
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
35
35
|
hasdivided = mbody.split("\n")
|
36
36
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
37
37
|
blanklines = 0 # (Integer) The number of blank lines
|
@@ -90,7 +90,7 @@ module Sisimai::Bite::Email
|
|
90
90
|
# * kijitora@example.com
|
91
91
|
if v['recipient']
|
92
92
|
# There are multiple recipient addresses in the message body.
|
93
|
-
dscontents << Sisimai::
|
93
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
94
94
|
v = dscontents[-1]
|
95
95
|
end
|
96
96
|
v['recipient'] = cv[1]
|
@@ -1,13 +1,13 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::X4 parses a bounce email which created by some qmail
|
3
3
|
# clone. Methods in the module are called from only Sisimai::Message.
|
4
4
|
module X4
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/X4.pm
|
7
7
|
# MTA module for qmail clones
|
8
|
-
require 'sisimai/
|
8
|
+
require 'sisimai/lhost'
|
9
9
|
|
10
|
-
Indicators = Sisimai::
|
10
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
11
11
|
StartingOf = {
|
12
12
|
error: ['Remote host said:'],
|
13
13
|
rfc822: ['--- Below this line is a copy of the message.', '--- Original message follows.'],
|
@@ -120,7 +120,7 @@ module Sisimai::Bite::Email
|
|
120
120
|
}.freeze
|
121
121
|
|
122
122
|
def description; return 'Unknown MTA #4'; end
|
123
|
-
def smtpagent; return Sisimai::
|
123
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
124
124
|
def headerlist; return []; end
|
125
125
|
|
126
126
|
# Parse bounce messages from Unknown MTA #4
|
@@ -134,9 +134,9 @@ module Sisimai::Bite::Email
|
|
134
134
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
135
135
|
# part or nil if it failed to parse or
|
136
136
|
# the arguments are missing
|
137
|
-
def
|
137
|
+
def make(mhead, mbody)
|
138
138
|
# Pre process email headers and the body part of the message which generated
|
139
|
-
# by qmail, see
|
139
|
+
# by qmail, see https://cr.yp.to/qmail.html
|
140
140
|
# e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000
|
141
141
|
# Subject: failure notice
|
142
142
|
tryto = %r/\A[(]qmail[ ]+\d+[ ]+invoked[ ]+for[ ]+bounce[)]/
|
@@ -145,7 +145,7 @@ module Sisimai::Bite::Email
|
|
145
145
|
match += 1 if mhead['received'].any? { |a| a =~ tryto }
|
146
146
|
return nil unless match > 0
|
147
147
|
|
148
|
-
dscontents = [Sisimai::
|
148
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
149
149
|
hasdivided = mbody.split("\n")
|
150
150
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
151
151
|
blanklines = 0 # (Integer) The number of blank lines
|
@@ -193,7 +193,7 @@ module Sisimai::Bite::Email
|
|
193
193
|
# <kijitora@example.jp>:
|
194
194
|
if v['recipient']
|
195
195
|
# There are multiple recipient addresses in the message body.
|
196
|
-
dscontents << Sisimai::
|
196
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
197
197
|
v = dscontents[-1]
|
198
198
|
end
|
199
199
|
v['recipient'] = cv[1]
|
@@ -1,19 +1,19 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::X5 parses a bounce email which created by Unknown
|
3
3
|
# MTA #5. Methods in the module are called from only Sisimai::Message.
|
4
4
|
module X5
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
7
|
-
require 'sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/X5.pm
|
7
|
+
require 'sisimai/lhost'
|
8
8
|
|
9
|
-
Indicators = Sisimai::
|
9
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
10
10
|
StartingOf = {
|
11
11
|
message: ['Content-Type: message/delivery-status'],
|
12
12
|
rfc822: ['Content-Type: message/rfc822'],
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
def description; return 'Unknown MTA #5'; end
|
16
|
-
def smtpagent; return Sisimai::
|
16
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
17
17
|
def headerlist; return []; end
|
18
18
|
|
19
19
|
# Parse bounce messages from Unknown MTA #5
|
@@ -27,7 +27,7 @@ module Sisimai::Bite::Email
|
|
27
27
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
28
28
|
# part or nil if it failed to parse or
|
29
29
|
# the arguments are missing
|
30
|
-
def
|
30
|
+
def make(mhead, mbody)
|
31
31
|
match = 0
|
32
32
|
match += 1 if mhead['to'].to_s.include?('NotificationRecipients')
|
33
33
|
if mhead['from'].include?('TWFpbCBEZWxpdmVyeSBTdWJzeXN0ZW0')
|
@@ -50,7 +50,7 @@ module Sisimai::Bite::Email
|
|
50
50
|
|
51
51
|
require 'sisimai/rfc1894'
|
52
52
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
53
|
-
dscontents = [Sisimai::
|
53
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
54
54
|
hasdivided = mbody.split("\n")
|
55
55
|
havepassed = ['']
|
56
56
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
@@ -97,7 +97,7 @@ module Sisimai::Bite::Email
|
|
97
97
|
# Final-Recipient: rfc822; kijitora@example.jp
|
98
98
|
if v['recipient']
|
99
99
|
# There are multiple recipient addresses in the message body.
|
100
|
-
dscontents << Sisimai::
|
100
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
101
101
|
v = dscontents[-1]
|
102
102
|
end
|
103
103
|
v['recipient'] = o[2]
|
@@ -1,19 +1,19 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::Yahoo parses a bounce email which created by Yahoo!
|
3
3
|
# MAIL. Methods in the module are called from only Sisimai::Message.
|
4
4
|
module Yahoo
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
7
|
-
require 'sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/Yahoo.pm
|
7
|
+
require 'sisimai/lhost'
|
8
8
|
|
9
|
-
Indicators = Sisimai::
|
9
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
10
10
|
StartingOf = {
|
11
11
|
message: ['Sorry, we were unable to deliver your message'],
|
12
12
|
rfc822: ['--- Below this line is a copy of the message.'],
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
def description; return 'Yahoo! MAIL: https://www.yahoo.com'; end
|
16
|
-
def smtpagent; return Sisimai::
|
16
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
17
17
|
|
18
18
|
# X-YMailISG: YtyUVyYWLDsbDh...
|
19
19
|
# X-YMail-JAS: Pb65aU4VM1mei...
|
@@ -32,11 +32,11 @@ module Sisimai::Bite::Email
|
|
32
32
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
33
33
|
# part or nil if it failed to parse or
|
34
34
|
# the arguments are missing
|
35
|
-
def
|
35
|
+
def make(mhead, mbody)
|
36
36
|
# :subject => %r/\AFailure Notice\z/,
|
37
37
|
return nil unless mhead['x-ymailisg']
|
38
38
|
|
39
|
-
dscontents = [Sisimai::
|
39
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
40
40
|
hasdivided = mbody.split("\n")
|
41
41
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
42
42
|
blanklines = 0 # (Integer) The number of blank lines
|
@@ -84,7 +84,7 @@ module Sisimai::Bite::Email
|
|
84
84
|
# <kijitora@example.org>:
|
85
85
|
if v['recipient']
|
86
86
|
# There are multiple recipient addresses in the message body.
|
87
|
-
dscontents << Sisimai::
|
87
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
88
88
|
v = dscontents[-1]
|
89
89
|
end
|
90
90
|
v['recipient'] = cv[1]
|
@@ -1,19 +1,19 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::Yandex parses a bounce email which created by
|
3
3
|
# Yandex.Mail. Methods in the module are called from only Sisimai::Message.
|
4
4
|
module Yandex
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
7
|
-
require 'sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/Yandex.pm
|
7
|
+
require 'sisimai/lhost'
|
8
8
|
|
9
|
-
Indicators = Sisimai::
|
9
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
10
10
|
StartingOf = {
|
11
11
|
message: ['This is the mail system at host yandex.ru.'],
|
12
12
|
rfc822: ['Content-Type: message/rfc822'],
|
13
13
|
}.freeze
|
14
14
|
|
15
|
-
def description; return 'Yandex.Mail:
|
16
|
-
def smtpagent; return Sisimai::
|
15
|
+
def description; return 'Yandex.Mail: https://www.yandex.ru'; end
|
16
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
17
17
|
|
18
18
|
# X-Yandex-Front: mxback1h.mail.yandex.net
|
19
19
|
# X-Yandex-TimeMark: 1417885948
|
@@ -35,7 +35,7 @@ module Sisimai::Bite::Email
|
|
35
35
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
36
36
|
# part or nil if it failed to parse or
|
37
37
|
# the arguments are missing
|
38
|
-
def
|
38
|
+
def make(mhead, mbody)
|
39
39
|
return nil unless mhead['x-yandex-uniq']
|
40
40
|
return nil unless mhead['from'] == 'mailer-daemon@yandex.ru'
|
41
41
|
|
@@ -43,7 +43,7 @@ module Sisimai::Bite::Email
|
|
43
43
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
44
44
|
permessage = {} # (Hash) Store values of each Per-Message field
|
45
45
|
|
46
|
-
dscontents = [Sisimai::
|
46
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
47
47
|
hasdivided = mbody.split("\n")
|
48
48
|
havepassed = ['']
|
49
49
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
@@ -99,7 +99,7 @@ module Sisimai::Bite::Email
|
|
99
99
|
# Final-Recipient: rfc822; kijitora@example.jp
|
100
100
|
if v['recipient']
|
101
101
|
# There are multiple recipient addresses in the message body.
|
102
|
-
dscontents << Sisimai::
|
102
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
103
103
|
v = dscontents[-1]
|
104
104
|
end
|
105
105
|
v['recipient'] = o[2]
|
@@ -1,12 +1,12 @@
|
|
1
|
-
module Sisimai::
|
2
|
-
# Sisimai::
|
1
|
+
module Sisimai::Lhost
|
2
|
+
# Sisimai::Lhost::Zoho parses a bounce email which created by Zoho Mail.
|
3
3
|
# Methods in the module are called from only Sisimai::Message.
|
4
4
|
module Zoho
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/
|
7
|
-
require 'sisimai/
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/Lhost/Zoho.pm
|
7
|
+
require 'sisimai/lhost'
|
8
8
|
|
9
|
-
Indicators = Sisimai::
|
9
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
10
10
|
StartingOf = {
|
11
11
|
message: ['This message was created automatically by mail delivery'],
|
12
12
|
rfc822: ['from mail.zoho.com by mx.zohomail.com'],
|
@@ -14,7 +14,7 @@ module Sisimai::Bite::Email
|
|
14
14
|
MessagesOf = { 'expired' => ['Host not reachable'] }.freeze
|
15
15
|
|
16
16
|
def description; return 'Zoho Mail: https://www.zoho.com'; end
|
17
|
-
def smtpagent; return Sisimai::
|
17
|
+
def smtpagent; return Sisimai::Lhost.smtpagent(self); end
|
18
18
|
|
19
19
|
# X-ZohoMail: Si CHF_MF_NL SS_10 UW48 UB48 FMWL UW48 UB48 SGR3_1_09124_42
|
20
20
|
# X-Zoho-Virus-Status: 2
|
@@ -32,7 +32,7 @@ module Sisimai::Bite::Email
|
|
32
32
|
# @return [Hash, Nil] Bounce data list and message/rfc822
|
33
33
|
# part or nil if it failed to parse or
|
34
34
|
# the arguments are missing
|
35
|
-
def
|
35
|
+
def make(mhead, mbody)
|
36
36
|
# :'from' => %r/mailer-daemon[@]mail[.]zoho[.]com\z/,
|
37
37
|
# :'subject' => %r{\A(?:
|
38
38
|
# Undelivered[ ]Mail[ ]Returned[ ]to[ ]Sender
|
@@ -42,7 +42,7 @@ module Sisimai::Bite::Email
|
|
42
42
|
# :'x-mailer' => %r/\AZoho Mail\z/,
|
43
43
|
return nil unless mhead['x-zohomail']
|
44
44
|
|
45
|
-
dscontents = [Sisimai::
|
45
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
46
46
|
hasdivided = mbody.split("\n")
|
47
47
|
rfc822list = [] # (Array) Each line in message/rfc822 part string
|
48
48
|
blanklines = 0 # (Integer) The number of blank lines
|
@@ -98,7 +98,7 @@ module Sisimai::Bite::Email
|
|
98
98
|
# kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.=
|
99
99
|
if v['recipient']
|
100
100
|
# There are multiple recipient addresses in the message body.
|
101
|
-
dscontents << Sisimai::
|
101
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
102
102
|
v = dscontents[-1]
|
103
103
|
end
|
104
104
|
v['recipient'] = cv[1]
|
@@ -116,7 +116,7 @@ module Sisimai::Bite::Email
|
|
116
116
|
# [Status: Error, Address: <kijitora@6kaku.example.co.jp>, ResponseCode 421, , Host not reachable.]
|
117
117
|
if v['recipient']
|
118
118
|
# There are multiple recipient addresses in the message body.
|
119
|
-
dscontents << Sisimai::
|
119
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
120
120
|
v = dscontents[-1]
|
121
121
|
end
|
122
122
|
v['recipient'] = cv[1]
|
data/lib/sisimai/mda.rb
CHANGED
@@ -85,7 +85,7 @@ module Sisimai
|
|
85
85
|
# @return [Hash, Nil] Bounce data list and message/rfc822 part
|
86
86
|
# or nil if it failed to parse or the
|
87
87
|
# arguments are missing
|
88
|
-
def
|
88
|
+
def make(mhead, mbody)
|
89
89
|
return nil unless mhead['from'].downcase.start_with?('mail delivery subsystem','mailer-daemon', 'postmaster')
|
90
90
|
|
91
91
|
agentname0 = '' # [String] MDA name
|
data/lib/sisimai/message.rb
CHANGED
@@ -5,21 +5,35 @@ module Sisimai
|
|
5
5
|
# method is not a bounce email, the method returns nil.
|
6
6
|
class Message
|
7
7
|
# Imported from p5-Sisimail/lib/Sisimai/Message.pm
|
8
|
+
require 'sisimai/arf'
|
9
|
+
require 'sisimai/mime'
|
8
10
|
require 'sisimai/order'
|
9
11
|
require 'sisimai/string'
|
10
12
|
require 'sisimai/address'
|
11
13
|
require 'sisimai/rfc5322'
|
14
|
+
require 'sisimai/rfc3834'
|
12
15
|
require 'sisimai/smtp/error'
|
13
16
|
|
14
17
|
@@rwaccessors = [
|
15
18
|
:from, # [String] UNIX From line
|
16
19
|
:header, # [Hash] Header part of an email
|
17
|
-
:ds, # [Array] Parsed data by Sisimai::
|
20
|
+
:ds, # [Array] Parsed data by Sisimai::Lhost::* module
|
18
21
|
:rfc822, # [Hash] Header part of the original message
|
19
|
-
:catch, # [
|
22
|
+
:catch, # [Any] The results returned by hook method
|
20
23
|
]
|
21
24
|
@@rwaccessors.each { |e| attr_accessor e }
|
22
25
|
|
26
|
+
DefaultSet = Sisimai::Order.another
|
27
|
+
ExtHeaders = Sisimai::Order.headers
|
28
|
+
SubjectTab = Sisimai::Order.by('subject')
|
29
|
+
RFC822Head = Sisimai::RFC5322.HEADERFIELDS
|
30
|
+
RFC3834Set = Sisimai::RFC3834.headerlist
|
31
|
+
HeaderList = %w[from to date subject content-type reply-to message-id
|
32
|
+
received content-transfer-encoding return-path x-mailer]
|
33
|
+
IsMultiple = { 'received' => true }
|
34
|
+
EndOfEmail = Sisimai::String.EOM
|
35
|
+
BorderLine = '__MIME_ENCODED_BOUNDARY__'
|
36
|
+
|
23
37
|
# Constructor of Sisimai::Message
|
24
38
|
# @param [String] data Email text data
|
25
39
|
# @param [Hash] argvs Module to be loaded
|
@@ -34,24 +48,13 @@ module Sisimai
|
|
34
48
|
return nil if data.empty?
|
35
49
|
|
36
50
|
email = data
|
37
|
-
input =
|
51
|
+
input = email.is_a?(Hash) ? 'json' : 'email'
|
38
52
|
field = argvs[:field] || []
|
39
|
-
child = nil
|
40
53
|
|
41
54
|
if input == 'email'
|
42
|
-
#
|
55
|
+
# Email message
|
43
56
|
return nil if email.empty?
|
44
57
|
email = email.scrub('?').gsub("\r\n", "\n")
|
45
|
-
child = 'Sisimai::Message::Email'
|
46
|
-
|
47
|
-
elsif input == 'json'
|
48
|
-
# Sisimai::Message::JSON
|
49
|
-
return nil unless email.is_a? Hash
|
50
|
-
child = 'Sisimai::Message::JSON'
|
51
|
-
else
|
52
|
-
# Unsupported value in "input"
|
53
|
-
warn ' ***warning: Unsupported value in "input": ' << input.to_s
|
54
|
-
return nil
|
55
58
|
end
|
56
59
|
|
57
60
|
unless field.is_a? Array
|
@@ -60,17 +63,11 @@ module Sisimai
|
|
60
63
|
return nil
|
61
64
|
end
|
62
65
|
|
63
|
-
begin
|
64
|
-
require child.gsub('::', '/').downcase
|
65
|
-
rescue LoadError => ce
|
66
|
-
warn ' ***warning: Failed to load module: ' << ce.to_s
|
67
|
-
return nil
|
68
|
-
end
|
69
|
-
|
70
66
|
methodargv = {
|
71
67
|
'data' => email,
|
72
68
|
'hook' => argvs[:hook] || nil,
|
73
69
|
'field' => field,
|
70
|
+
'input' => input,
|
74
71
|
}
|
75
72
|
[:load, :order].each do |e|
|
76
73
|
# Order of MTA modules
|
@@ -80,7 +77,7 @@ module Sisimai
|
|
80
77
|
methodargv[e.to_s] = argvs[e]
|
81
78
|
end
|
82
79
|
|
83
|
-
datasource =
|
80
|
+
datasource = Sisimai::Message.make(methodargv)
|
84
81
|
return nil unless datasource
|
85
82
|
return nil unless datasource.key?('ds')
|
86
83
|
|
@@ -98,21 +95,531 @@ module Sisimai
|
|
98
95
|
return false
|
99
96
|
end
|
100
97
|
|
101
|
-
# Make data structure
|
102
|
-
|
103
|
-
|
98
|
+
# Make data structure from the email message(a body part and headers)
|
99
|
+
# @param [Hash] argvs Email data
|
100
|
+
# @options argvs [String] data Entire email message
|
101
|
+
# @options argvs [Array] load User defined MTA module list
|
102
|
+
# @options argvs [Array] field Email header names to be captured
|
103
|
+
# @options argvs [Array] order The order of MTA modules
|
104
|
+
# @options argvs [Code] hook Reference to callback method
|
105
|
+
# @return [Hash] Resolved data structure
|
106
|
+
def self.make(argvs)
|
107
|
+
email = argvs['data']
|
108
|
+
|
109
|
+
hookmethod = argvs['hook'] || nil
|
110
|
+
processing = {
|
104
111
|
'from' => '', # From_ line
|
105
112
|
'header' => {}, # Email header
|
106
113
|
'rfc822' => '', # Original message part
|
107
114
|
'ds' => [], # Parsed data, Delivery Status
|
108
115
|
'catch' => nil, # Data parsed by callback method
|
109
116
|
}
|
117
|
+
methodargv = {
|
118
|
+
'load' => argvs['load'] || [],
|
119
|
+
'order' => argvs['order'] || []
|
120
|
+
}
|
121
|
+
tobeloaded = Sisimai::Message.load(methodargv)
|
122
|
+
|
123
|
+
if argvs['input'] == 'email'
|
124
|
+
# Email message
|
125
|
+
# 1. Split email data to headers and a body part.
|
126
|
+
return nil unless aftersplit = Sisimai::Message.divideup(email)
|
127
|
+
|
128
|
+
# 2. Convert email headers from text to hash reference
|
129
|
+
headerargv = {
|
130
|
+
'extheaders' => ExtHeaders,
|
131
|
+
'tryonfirst' => [],
|
132
|
+
'extrafield' => argvs['field'] || [],
|
133
|
+
}
|
134
|
+
processing['from'] = aftersplit['from']
|
135
|
+
processing['header'] = Sisimai::Message.headers(aftersplit['header'], headerargv)
|
136
|
+
|
137
|
+
# 3. Check headers for detecting MTA modules
|
138
|
+
if headerargv['tryonfirst'].empty?
|
139
|
+
headerargv['tryonfirst'] += Sisimai::Message.makeorder(processing['header'])
|
140
|
+
end
|
141
|
+
|
142
|
+
# 4. Rewrite message body for detecting the bounce reason
|
143
|
+
methodargv = {
|
144
|
+
'hook' => hookmethod,
|
145
|
+
'mail' => processing,
|
146
|
+
'body' => aftersplit['body'],
|
147
|
+
'tryonfirst' => headerargv['tryonfirst'],
|
148
|
+
'tobeloaded' => tobeloaded,
|
149
|
+
}
|
150
|
+
return nil unless bouncedata = Sisimai::Message.parse(methodargv)
|
151
|
+
else
|
152
|
+
# JSON object
|
153
|
+
methodargv = {
|
154
|
+
'hook' => hookmethod,
|
155
|
+
'json' => argvs['data'],
|
156
|
+
'tobeloaded' => tobeloaded.concat(Sisimai::Order.forjson),
|
157
|
+
}
|
158
|
+
return nil unless bouncedata = Sisimai::Message.adapt(methodargv)
|
159
|
+
end
|
160
|
+
return nil if bouncedata.empty?
|
161
|
+
|
162
|
+
%w|ds catch rfc822|.each { |e| processing[e] = bouncedata[e] }
|
163
|
+
if argvs['input'] == 'email'
|
164
|
+
# 5. Rewrite headers of the original message in the body part
|
165
|
+
p = bouncedata['rfc822']
|
166
|
+
p = aftersplit['body'] if p.empty?
|
167
|
+
processing['rfc822'] = p.is_a?(::String) ? Sisimai::Message.takeapart(p) : p
|
168
|
+
end
|
169
|
+
|
170
|
+
return processing
|
110
171
|
end
|
111
172
|
|
112
173
|
# Load MTA modules which specified at 'order' and 'load' in the argument
|
113
|
-
#
|
174
|
+
# @param [Hash] argvs Module information to be loaded
|
175
|
+
# @options argvs [Array] load User defined MTA module list
|
176
|
+
# @options argvs [Array] order The order of MTA modules
|
177
|
+
# @return [Array] Module list
|
114
178
|
# @since v4.20.0
|
115
|
-
def load
|
179
|
+
def self.load(argvs)
|
180
|
+
modulelist = []
|
181
|
+
tobeloaded = []
|
182
|
+
|
183
|
+
%w[load order].each do |e|
|
184
|
+
# The order of MTA modules specified by user
|
185
|
+
next unless argvs.key?(e)
|
186
|
+
next unless argvs[e].is_a? Array
|
187
|
+
next if argvs[e].empty?
|
188
|
+
|
189
|
+
modulelist += argvs['order'] if e == 'order'
|
190
|
+
next unless e == 'load'
|
191
|
+
|
192
|
+
# Load user defined MTA module
|
193
|
+
argvs['load'].each do |v|
|
194
|
+
# Load user defined MTA module
|
195
|
+
begin
|
196
|
+
require v.to_s.gsub('::', '/').downcase
|
197
|
+
rescue LoadError
|
198
|
+
warn ' ***warning: Failed to load ' << v
|
199
|
+
next
|
200
|
+
end
|
201
|
+
next unless argvs.input == 'email'
|
202
|
+
|
203
|
+
Module.const_get(v).headerlist.each do |w|
|
204
|
+
# Get header name which required user defined MTA module
|
205
|
+
ExtHeaders[w] ||= {}
|
206
|
+
ExtHeaders[w][v] = 1
|
207
|
+
end
|
208
|
+
tobeloaded << v
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
while e = modulelist.shift do
|
213
|
+
# Append the custom order of MTA modules
|
214
|
+
next if tobeloaded.index(e)
|
215
|
+
tobeloaded << e
|
216
|
+
end
|
217
|
+
|
218
|
+
return tobeloaded
|
219
|
+
end
|
220
|
+
|
221
|
+
# Check headers for detecting MTA module and returns the order of modules
|
222
|
+
# @param [Hash] heads Email header data
|
223
|
+
# @return [Array] Order of MTA modules
|
224
|
+
def self.makeorder(heads)
|
225
|
+
return [] unless heads
|
226
|
+
return [] unless heads['subject']
|
227
|
+
return [] if heads['subject'].empty?
|
228
|
+
order = []
|
229
|
+
|
230
|
+
# Try to match the value of "Subject" with patterns generated by
|
231
|
+
# Sisimai::Order->by('subject') method
|
232
|
+
title = heads['subject'].downcase
|
233
|
+
SubjectTab.each_key do |e|
|
234
|
+
# Get MTA list from the subject header
|
235
|
+
next unless title.include?(e)
|
236
|
+
order += SubjectTab[e] # Matched and push MTA list
|
237
|
+
break
|
238
|
+
end
|
239
|
+
return order
|
240
|
+
end
|
241
|
+
|
242
|
+
# Convert email headers from text to hash reference
|
243
|
+
# @param [String] heads Email header data
|
244
|
+
# @param [Hash] argvs
|
245
|
+
# @param options extheaders [Array] External header table
|
246
|
+
# @return [Hash] Structured email header data
|
247
|
+
def self.headers(heads, argvs = {})
|
248
|
+
return nil unless heads
|
249
|
+
|
250
|
+
currheader = ''
|
251
|
+
allheaders = {}
|
252
|
+
structured = {}
|
253
|
+
extheaders = argvs['extheaders'] || []
|
254
|
+
extrafield = argvs['extrafield'] || []
|
255
|
+
hasdivided = heads.split("\n")
|
256
|
+
|
257
|
+
HeaderList.each { |e| structured[e] = nil }
|
258
|
+
HeaderList.each { |e| allheaders[e] = true }
|
259
|
+
RFC3834Set.each { |e| allheaders[e] = true }
|
260
|
+
IsMultiple.each_key { |e| structured[e] = [] }
|
261
|
+
extheaders.each_key { |e| allheaders[e] = true }
|
262
|
+
unless extrafield.empty?
|
263
|
+
extrafield.each { |e| allheaders[e] = true }
|
264
|
+
end
|
265
|
+
|
266
|
+
while e = hasdivided.shift do
|
267
|
+
# Convert email headers to hash
|
268
|
+
if cv = e.match(/\A[ \t]+(.+)\z/)
|
269
|
+
# Continued (foled) header value from the previous line
|
270
|
+
next unless allheaders.key?(currheader)
|
271
|
+
|
272
|
+
# Header line continued from the previous line
|
273
|
+
if structured[currheader].is_a? Array
|
274
|
+
# Concatenate a header which have multi-lines such as 'Received'
|
275
|
+
structured[currheader][-1] << ' ' << cv[1]
|
276
|
+
else
|
277
|
+
structured[currheader] ||= ''
|
278
|
+
structured[currheader] << ' ' << cv[1]
|
279
|
+
end
|
280
|
+
else
|
281
|
+
# split the line into a header name and a header content
|
282
|
+
(lhs, rhs) = e.split(/:[ ]*/, 2)
|
283
|
+
currheader = lhs ? lhs.downcase : ''
|
284
|
+
next unless allheaders.key?(currheader)
|
285
|
+
|
286
|
+
if IsMultiple.key?(currheader)
|
287
|
+
# Such as 'Received' header, there are multiple headers in a single
|
288
|
+
# email message.
|
289
|
+
#rhs = rhs.tr("\t", ' ').squeeze(' ')
|
290
|
+
rhs = rhs.tr("\t", ' ')
|
291
|
+
structured[currheader] << rhs
|
292
|
+
else
|
293
|
+
# Other headers except "Received" and so on
|
294
|
+
if extheaders[currheader]
|
295
|
+
# MTA specific header
|
296
|
+
extheaders[currheader].each do |r|
|
297
|
+
next if argvs['tryonfirst'].index(r)
|
298
|
+
argvs['tryonfirst'] << r
|
299
|
+
end
|
300
|
+
end
|
301
|
+
structured[currheader] = rhs
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
return structured
|
306
|
+
end
|
307
|
+
|
308
|
+
# Divide email data up headers and a body part.
|
309
|
+
# @param [String] email Email data
|
310
|
+
# @return [Hash] Email data after split
|
311
|
+
def self.divideup(email)
|
312
|
+
return nil if email.empty?
|
313
|
+
|
314
|
+
block = { 'from' => '', 'header' => '', 'body' => '' }
|
315
|
+
email.scrub!('?')
|
316
|
+
email.gsub!(/\r\n/, "\n") if email.include?("\r\n")
|
317
|
+
email.gsub!(/[ \t]+$/, '') if email =~ /[ \t]+$/
|
318
|
+
|
319
|
+
(block['header'], block['body']) = email.split(/\n\n/, 2)
|
320
|
+
return nil unless block['header']
|
321
|
+
return nil unless block['body']
|
322
|
+
|
323
|
+
if block['header'][0, 5] == 'From '
|
324
|
+
# From MAILER-DAEMON Tue Feb 11 00:00:00 2014
|
325
|
+
block['from'] = block['header'].split(/\n/, 2)[0].delete("\r")
|
326
|
+
else
|
327
|
+
# Set pseudo UNIX From line
|
328
|
+
block['from'] = 'MAILER-DAEMON Tue Feb 11 00:00:00 2014'
|
329
|
+
end
|
330
|
+
|
331
|
+
block['body'] << "\n"
|
332
|
+
return block
|
333
|
+
end
|
334
|
+
|
335
|
+
# Take each email header in the original message apart
|
336
|
+
# @param [String] heads The original message header
|
337
|
+
# @return [Hash] Structured message headers
|
338
|
+
def self.takeapart(heads)
|
339
|
+
return {} unless heads
|
340
|
+
|
341
|
+
# 1. Scrub to avoid "invalid byte sequence in UTF-8" exception (#82)
|
342
|
+
# 2. Convert from string to hash reference
|
343
|
+
heads = heads.scrub('?').gsub(/^[>]+[ ]/m, '').gsub(/=[ ]+=/, "=\n =")
|
344
|
+
|
345
|
+
previousfn = '' # Previous field name
|
346
|
+
asciiarmor = {} # Header names which has MIME encoded value
|
347
|
+
headerpart = {} # Required headers in the original message part
|
348
|
+
hasdivided = heads.split("\n")
|
349
|
+
|
350
|
+
while e = hasdivided.shift do
|
351
|
+
# Header name as a key, The value of header as a value
|
352
|
+
if e.start_with?(' ', "\t")
|
353
|
+
# Continued (foled) header value from the previous line
|
354
|
+
next if previousfn.empty?
|
355
|
+
|
356
|
+
# Concatenate the line if it is the value of required header
|
357
|
+
if Sisimai::MIME.is_mimeencoded(e)
|
358
|
+
# The line is MIME-Encoded test
|
359
|
+
headerpart[previousfn] << if previousfn == 'subject'
|
360
|
+
# Subject: header
|
361
|
+
BorderLine + e
|
362
|
+
else
|
363
|
+
# Is not Subject header
|
364
|
+
e
|
365
|
+
end
|
366
|
+
asciiarmor[previousfn] = true
|
367
|
+
else
|
368
|
+
# ASCII Characters only: Not MIME-Encoded
|
369
|
+
headerpart[previousfn] << e.lstrip
|
370
|
+
asciiarmor[previousfn] ||= false
|
371
|
+
end
|
372
|
+
else
|
373
|
+
# Header name as a key, The value of header as a value
|
374
|
+
(lhs, rhs) = e.split(/:[ ]*/, 2)
|
375
|
+
next unless lhs
|
376
|
+
lhs.downcase!
|
377
|
+
previousfn = ''
|
378
|
+
|
379
|
+
next unless RFC822Head.key?(lhs)
|
380
|
+
previousfn = lhs
|
381
|
+
headerpart[previousfn] = rhs unless headerpart[previousfn]
|
382
|
+
end
|
383
|
+
end
|
384
|
+
return headerpart unless headerpart['subject']
|
385
|
+
|
386
|
+
# Convert MIME-Encoded subject
|
387
|
+
if Sisimai::String.is_8bit(headerpart['subject'])
|
388
|
+
# The value of ``Subject'' header is including multibyte character,
|
389
|
+
# is not MIME-Encoded text.
|
390
|
+
headerpart['subject'].scrub!('?')
|
391
|
+
else
|
392
|
+
# MIME-Encoded subject field or ASCII characters only
|
393
|
+
r = []
|
394
|
+
if asciiarmor['subject']
|
395
|
+
# split the value of Subject by borderline
|
396
|
+
headerpart['subject'].split(BorderLine).each do |v|
|
397
|
+
# Insert value to the array if the string is MIME encoded text
|
398
|
+
r << v if Sisimai::MIME.is_mimeencoded(v)
|
399
|
+
end
|
400
|
+
else
|
401
|
+
# Subject line is not MIME encoded
|
402
|
+
r << headerpart['subject']
|
403
|
+
end
|
404
|
+
headerpart['subject'] = Sisimai::MIME.mimedecode(r)
|
405
|
+
end
|
406
|
+
return headerpart
|
407
|
+
end
|
408
|
+
|
409
|
+
# @abstract Parse bounce mail with each MTA module
|
410
|
+
# @param [Hash] argvs Processing message entity.
|
411
|
+
# @param options argvs [Hash] mail Email message entity
|
412
|
+
# @param options mail [String] from From line of mbox
|
413
|
+
# @param options mail [Hash] header Email header data
|
414
|
+
# @param options mail [String] rfc822 Original message part
|
415
|
+
# @param options mail [Array] ds Delivery status list(parsed data)
|
416
|
+
# @param options argvs [String] body Email message body
|
417
|
+
# @param options argvs [Array] tryonfirst MTA module list to load on first
|
418
|
+
# @param options argvs [Array] tobeloaded User defined MTA module list
|
419
|
+
# @return [Hash] Parsed and structured bounce mails
|
420
|
+
def self.parse(argvs)
|
421
|
+
return nil unless argvs['mail']
|
422
|
+
return nil unless argvs['body']
|
423
|
+
|
424
|
+
mailheader = argvs['mail']['header']
|
425
|
+
bodystring = argvs['body']
|
426
|
+
hookmethod = argvs['hook'] || nil
|
427
|
+
havecaught = nil
|
428
|
+
|
429
|
+
return nil unless mailheader
|
430
|
+
|
431
|
+
# PRECHECK_EACH_HEADER:
|
432
|
+
# Set empty string if the value is nil
|
433
|
+
mailheader['from'] ||= ''
|
434
|
+
mailheader['subject'] ||= ''
|
435
|
+
mailheader['content-type'] ||= ''
|
436
|
+
|
437
|
+
# Decode BASE64 Encoded message body, rewrite.
|
438
|
+
mesgformat = (mailheader['content-type'] || '').downcase
|
439
|
+
ctencoding = (mailheader['content-transfer-encoding'] || '').downcase
|
440
|
+
|
441
|
+
if mesgformat.start_with?('text/plain', 'text/html')
|
442
|
+
# Content-Type: text/plain; charset=UTF-8
|
443
|
+
if ctencoding == 'base64'
|
444
|
+
# Content-Transfer-Encoding: base64
|
445
|
+
bodystring = Sisimai::MIME.base64d(bodystring)
|
446
|
+
|
447
|
+
elsif ctencoding == 'quoted-printable'
|
448
|
+
# Content-Transfer-Encoding: quoted-printable
|
449
|
+
bodystring = Sisimai::MIME.qprintd(bodystring)
|
450
|
+
end
|
451
|
+
|
452
|
+
if mesgformat.start_with?('text/html;')
|
453
|
+
# Content-Type: text/html;...
|
454
|
+
bodystring = Sisimai::String.to_plain(bodystring, true)
|
455
|
+
end
|
456
|
+
else
|
457
|
+
# NOT text/plain
|
458
|
+
if mesgformat.start_with?('multipart/')
|
459
|
+
# In case of Content-Type: multipart/*
|
460
|
+
p = Sisimai::MIME.makeflat(mailheader['content-type'], bodystring)
|
461
|
+
bodystring = p unless p.empty?
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# EXPAND_FORWARDED_MESSAGE:
|
466
|
+
# Check whether or not the message is a bounce mail.
|
467
|
+
# Pre-Process email body if it is a forwarded bounce message.
|
468
|
+
# Get the original text when the subject begins from 'fwd:' or 'fw:'
|
469
|
+
if mailheader['subject'].downcase =~ /\A[ \t]*fwd?:/
|
470
|
+
# Delete quoted strings, quote symbols(>)
|
471
|
+
bodystring = bodystring.gsub(/^[>]+[ ]/m, '').gsub(/^[>]$/m, '')
|
472
|
+
elsif Sisimai::MIME.is_mimeencoded(mailheader['subject'])
|
473
|
+
# Decode MIME-Encoded "Subject:" header
|
474
|
+
mailheader['subject'] = Sisimai::MIME.mimedecode(mailheader['subject'].split(/[ ]/))
|
475
|
+
mailheader['subject'].scrub!('?')
|
476
|
+
end
|
477
|
+
bodystring = bodystring.scrub('?').delete("\r")
|
478
|
+
|
479
|
+
if hookmethod.is_a? Proc
|
480
|
+
# Call the hook method
|
481
|
+
begin
|
482
|
+
p = {
|
483
|
+
'datasrc' => 'email',
|
484
|
+
'headers' => mailheader,
|
485
|
+
'message' => bodystring,
|
486
|
+
'bounces' => nil
|
487
|
+
}
|
488
|
+
havecaught = hookmethod.call(p)
|
489
|
+
rescue StandardError => ce
|
490
|
+
warn ' ***warning: Something is wrong in hook method :' << ce.to_s
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
bodystring << EndOfEmail
|
495
|
+
haveloaded = {}
|
496
|
+
parseddata = nil
|
497
|
+
|
498
|
+
catch :PARSER do
|
499
|
+
while true
|
500
|
+
# 1. Sisimai::ARF
|
501
|
+
# 2. User-Defined Module
|
502
|
+
# 3. MTA Module Candidates to be tried on first
|
503
|
+
# 4. Sisimai::Lhost::*
|
504
|
+
# 5. Sisimai::RFC3464
|
505
|
+
# 6. Sisimai::RFC3834
|
506
|
+
if Sisimai::ARF.is_arf(mailheader)
|
507
|
+
# Feedback Loop message
|
508
|
+
parseddata = Sisimai::ARF.make(mailheader, bodystring)
|
509
|
+
throw :PARSER if parseddata
|
510
|
+
end
|
511
|
+
|
512
|
+
while r = argvs['tobeloaded'].shift do
|
513
|
+
# Call user defined MTA modules
|
514
|
+
next if haveloaded[r]
|
515
|
+
parseddata = Module.const_get(r).make(mailheader, bodystring)
|
516
|
+
haveloaded[r] = true
|
517
|
+
throw :PARSER if parseddata
|
518
|
+
end
|
519
|
+
|
520
|
+
argvs['tryonfirst'].concat(DefaultSet)
|
521
|
+
while r = argvs['tryonfirst'].shift do
|
522
|
+
# Try MTA module candidates
|
523
|
+
next if haveloaded.key?(r)
|
524
|
+
require r.gsub('::', '/').downcase
|
525
|
+
parseddata = Module.const_get(r).make(mailheader, bodystring)
|
526
|
+
haveloaded[r] = true
|
527
|
+
throw :PARSER if parseddata
|
528
|
+
end
|
529
|
+
|
530
|
+
# When the all of Sisimai::Lhost::* modules did not return bounce
|
531
|
+
# data, call Sisimai::RFC3464;
|
532
|
+
require 'sisimai/rfc3464'
|
533
|
+
parseddata = Sisimai::RFC3464.make(mailheader, bodystring)
|
534
|
+
break if parseddata
|
535
|
+
|
536
|
+
# Try to parse the message as auto reply message defined in RFC3834
|
537
|
+
require 'sisimai/rfc3834'
|
538
|
+
parseddata = Sisimai::RFC3834.make(mailheader, bodystring)
|
539
|
+
break if parseddata
|
540
|
+
|
541
|
+
# as of now, we have no sample email for coding this block
|
542
|
+
break
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
parseddata['catch'] = havecaught if parseddata
|
547
|
+
return parseddata
|
548
|
+
end
|
549
|
+
|
550
|
+
# Parse bounce object with each MTA(JSON) module
|
551
|
+
# @param [Hash] argvs Processing message entity.
|
552
|
+
# @param options argvs [Hash] json Decoded bounce object
|
553
|
+
# @param options argvs [Proc] hook Hook method to be called
|
554
|
+
# @return [Hash] Parsed and structured bounce mails
|
555
|
+
def self.adapt(argvs)
|
556
|
+
Sisimai::Message.warn(self.name, 'gone')
|
557
|
+
bouncedata = argvs['json'] || {}
|
558
|
+
hookmethod = argvs['hook'] || nil
|
559
|
+
havecaught = nil
|
560
|
+
haveloaded = {}
|
561
|
+
parseddata = nil
|
562
|
+
|
563
|
+
# Call the hook method
|
564
|
+
if hookmethod.is_a? Proc
|
565
|
+
# Execute hook method
|
566
|
+
begin
|
567
|
+
p = {
|
568
|
+
'datasrc' => 'json',
|
569
|
+
'headers' => nil,
|
570
|
+
'message' => nil,
|
571
|
+
'bounces' => argvs['json']
|
572
|
+
}
|
573
|
+
havecaught = hookmethod.call(p)
|
574
|
+
rescue StandardError => ce
|
575
|
+
warn ' ***warning: Something is wrong in hook method :' << ce.to_s
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
catch :ADAPTOR do
|
580
|
+
while true
|
581
|
+
# 1. User-Defined Module
|
582
|
+
# 2. MTA(JSON) Module Candidates to be tried on first
|
583
|
+
# 3. Sisimai::Lhost::*
|
584
|
+
#
|
585
|
+
argvs['tobeloaded'].each do |r|
|
586
|
+
# Call user defined MTA(JSON) modules
|
587
|
+
next if haveloaded[r]
|
588
|
+
begin
|
589
|
+
require r.gsub('::', '/').downcase
|
590
|
+
rescue LoadError => ce
|
591
|
+
warn ' ***warning: Failed to load ' << ce.to_s
|
592
|
+
next
|
593
|
+
end
|
594
|
+
parseddata = Module.const_get(r).json(bouncedata)
|
595
|
+
haveloaded[r] = true
|
596
|
+
throw :ADAPTOR if parseddata
|
597
|
+
end
|
598
|
+
|
599
|
+
break # as of now, we have no sample JSON data for coding this block
|
600
|
+
end
|
601
|
+
end
|
602
|
+
return nil unless parseddata
|
603
|
+
return nil unless parseddata['ds']
|
604
|
+
|
605
|
+
parseddata['catch'] = havecaught
|
606
|
+
parseddata['ds'].each { |e| e['agent'].sub!(/\AEmail::/, 'JSON::') }
|
607
|
+
return parseddata
|
608
|
+
end
|
609
|
+
|
610
|
+
# @abstract Print warnings about an obsoleted method. This method will be
|
611
|
+
# removed at the future release of Sisimai
|
612
|
+
# @until v4.25.5
|
613
|
+
def self.warn(whois = '', useit = nil)
|
614
|
+
label = ' ***warning:'
|
615
|
+
methodname = caller[0][/`.*'/][1..-2]
|
616
|
+
messageset = sprintf("%s %s.%s is marked as obsoleted", label, whois, methodname)
|
617
|
+
|
618
|
+
useit ||= methodname
|
619
|
+
messageset << sprintf(" and will be removed at %s.", Sisimai::Lhost.removedat)
|
620
|
+
messageset << sprintf(" Use %s.%s instead.\n", self.name, useit) if useit != 'gone'
|
621
|
+
Kernel.warn messageset
|
622
|
+
end
|
116
623
|
|
117
624
|
end
|
118
625
|
end
|