sisimai 4.25.3-java → 4.25.4-java

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.

Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -0
  4. data/ANALYTICAL-PRECISION +2 -4
  5. data/CONTRIBUTING +4 -0
  6. data/ChangeLog.md +39 -0
  7. data/Developers.mk +1 -72
  8. data/Makefile +4 -0
  9. data/README-JA.md +3 -0
  10. data/README.md +5 -2
  11. data/lib/sisimai.rb +4 -3
  12. data/lib/sisimai/arf.rb +6 -6
  13. data/lib/sisimai/bite.rb +8 -3
  14. data/lib/sisimai/bite/email.rb +7 -50
  15. data/lib/sisimai/bite/json.rb +5 -25
  16. data/lib/sisimai/data.rb +1 -1
  17. data/lib/sisimai/lhost.rb +101 -0
  18. data/lib/sisimai/{bite/email → lhost}/activehunter.rb +10 -10
  19. data/lib/sisimai/{bite/email → lhost}/amavis.rb +9 -9
  20. data/lib/sisimai/lhost/amazonses.rb +403 -0
  21. data/lib/sisimai/{bite/email → lhost}/amazonworkmail.rb +9 -9
  22. data/lib/sisimai/{bite/email → lhost}/aol.rb +10 -10
  23. data/lib/sisimai/{bite/email → lhost}/apachejames.rb +10 -10
  24. data/lib/sisimai/{bite/email → lhost}/bigfoot.rb +9 -9
  25. data/lib/sisimai/{bite/email → lhost}/biglobe.rb +10 -10
  26. data/lib/sisimai/{bite/email → lhost}/courier.rb +10 -10
  27. data/lib/sisimai/{bite/email → lhost}/domino.rb +9 -9
  28. data/lib/sisimai/{bite/email → lhost}/einsundeins.rb +10 -10
  29. data/lib/sisimai/{bite/email → lhost}/exchange2003.rb +9 -9
  30. data/lib/sisimai/{bite/email → lhost}/exchange2007.rb +9 -9
  31. data/lib/sisimai/{bite/email → lhost}/exim.rb +10 -10
  32. data/lib/sisimai/{bite/email → lhost}/ezweb.rb +9 -9
  33. data/lib/sisimai/{bite/email → lhost}/facebook.rb +9 -9
  34. data/lib/sisimai/{bite/email → lhost}/fml.rb +10 -10
  35. data/lib/sisimai/{bite/email → lhost}/gmx.rb +10 -10
  36. data/lib/sisimai/{bite/email → lhost}/google.rb +14 -14
  37. data/lib/sisimai/{bite/email → lhost}/gsuite.rb +10 -10
  38. data/lib/sisimai/{bite/email → lhost}/imailserver.rb +10 -10
  39. data/lib/sisimai/{bite/email → lhost}/interscanmss.rb +9 -9
  40. data/lib/sisimai/{bite/email → lhost}/kddi.rb +10 -10
  41. data/lib/sisimai/{bite/email → lhost}/mailfoundry.rb +9 -9
  42. data/lib/sisimai/{bite/email → lhost}/mailmarshalsmtp.rb +9 -9
  43. data/lib/sisimai/{bite/email → lhost}/mailru.rb +11 -11
  44. data/lib/sisimai/{bite/email → lhost}/mcafee.rb +9 -9
  45. data/lib/sisimai/{bite/email → lhost}/messagelabs.rb +9 -9
  46. data/lib/sisimai/{bite/email → lhost}/messagingserver.rb +9 -9
  47. data/lib/sisimai/{bite/email → lhost}/mfilter.rb +9 -9
  48. data/lib/sisimai/{bite/email → lhost}/mxlogic.rb +10 -10
  49. data/lib/sisimai/{bite/email → lhost}/notes.rb +9 -9
  50. data/lib/sisimai/{bite/email → lhost}/office365.rb +10 -10
  51. data/lib/sisimai/{bite/email → lhost}/opensmtpd.rb +9 -9
  52. data/lib/sisimai/{bite/email → lhost}/outlook.rb +9 -9
  53. data/lib/sisimai/{bite/email → lhost}/postfix.rb +9 -9
  54. data/lib/sisimai/{bite/email → lhost}/qmail.rb +9 -9
  55. data/lib/sisimai/{bite/email → lhost}/receivingses.rb +11 -11
  56. data/lib/sisimai/{bite/email → lhost}/sendgrid.rb +120 -11
  57. data/lib/sisimai/{bite/email → lhost}/sendmail.rb +9 -9
  58. data/lib/sisimai/{bite/email → lhost}/surfcontrol.rb +9 -9
  59. data/lib/sisimai/{bite/email → lhost}/userdefined.rb +9 -9
  60. data/lib/sisimai/{bite/email → lhost}/v5sendmail.rb +10 -10
  61. data/lib/sisimai/{bite/email → lhost}/verizon.rb +11 -11
  62. data/lib/sisimai/{bite/email → lhost}/x1.rb +9 -9
  63. data/lib/sisimai/{bite/email → lhost}/x2.rb +9 -9
  64. data/lib/sisimai/{bite/email → lhost}/x3.rb +9 -9
  65. data/lib/sisimai/{bite/email → lhost}/x4.rb +10 -10
  66. data/lib/sisimai/{bite/email → lhost}/x5.rb +9 -9
  67. data/lib/sisimai/{bite/email → lhost}/yahoo.rb +9 -9
  68. data/lib/sisimai/{bite/email → lhost}/yandex.rb +10 -10
  69. data/lib/sisimai/{bite/email → lhost}/zoho.rb +10 -10
  70. data/lib/sisimai/mda.rb +1 -1
  71. data/lib/sisimai/message.rb +535 -28
  72. data/lib/sisimai/message/email.rb +3 -454
  73. data/lib/sisimai/message/json.rb +3 -177
  74. data/lib/sisimai/order.rb +238 -4
  75. data/lib/sisimai/order/email.rb +5 -190
  76. data/lib/sisimai/order/json.rb +3 -39
  77. data/lib/sisimai/reason.rb +1 -1
  78. data/lib/sisimai/reason/norelaying.rb +1 -1
  79. data/lib/sisimai/reason/userunknown.rb +1 -1
  80. data/lib/sisimai/reason/virusdetected.rb +1 -0
  81. data/lib/sisimai/rfc1894.rb +2 -2
  82. data/lib/sisimai/rfc3464.rb +13 -13
  83. data/lib/sisimai/rfc3834.rb +3 -3
  84. data/lib/sisimai/rfc5322.rb +1 -1
  85. data/lib/sisimai/rhost.rb +5 -4
  86. data/lib/sisimai/rhost/exchangeonline.rb +1 -1
  87. data/lib/sisimai/rhost/franceptt.rb +1 -1
  88. data/lib/sisimai/rhost/godaddy.rb +1 -1
  89. data/lib/sisimai/rhost/iua.rb +39 -0
  90. data/lib/sisimai/version.rb +1 -1
  91. data/set-of-emails/README.md +0 -1
  92. data/set-of-emails/maildir/bsd/email-gsuite-13.eml +261 -0
  93. data/set-of-emails/maildir/bsd/email-postfix-62.eml +105 -0
  94. data/set-of-emails/maildir/bsd/email-postfix-63.eml +85 -0
  95. data/set-of-emails/maildir/bsd/rhost-iua-01.eml +70 -0
  96. metadata +75 -94
  97. data/fig/sisimai-architecture.png +0 -0
  98. data/lib/sisimai/bite/email/amazonses.rb +0 -186
  99. data/lib/sisimai/bite/json/amazonses.rb +0 -242
  100. data/lib/sisimai/bite/json/sendgrid.rb +0 -123
  101. data/set-of-emails/jsonobj/json-amazonses-01.json +0 -1
  102. data/set-of-emails/jsonobj/json-amazonses-02.json +0 -11
  103. data/set-of-emails/jsonobj/json-amazonses-03.json +0 -1
  104. data/set-of-emails/jsonobj/json-amazonses-04.json +0 -1
  105. data/set-of-emails/jsonobj/json-amazonses-05.json +0 -1
  106. data/set-of-emails/jsonobj/json-amazonses-06.json +0 -1
  107. data/set-of-emails/jsonobj/json-sendgrid-01.json +0 -1
  108. data/set-of-emails/jsonobj/json-sendgrid-02.json +0 -1
  109. data/set-of-emails/jsonobj/json-sendgrid-03.json +0 -1
  110. data/set-of-emails/jsonobj/json-sendgrid-04.json +0 -1
  111. data/set-of-emails/jsonobj/json-sendgrid-05.json +0 -1
  112. data/set-of-emails/jsonobj/json-sendgrid-06.json +0 -1
  113. data/set-of-emails/jsonobj/json-sendgrid-07.json +0 -1
  114. data/set-of-emails/jsonobj/json-sendgrid-08.json +0 -1
  115. data/set-of-emails/jsonobj/json-sendgrid-09.json +0 -1
  116. data/set-of-emails/jsonobj/json-sendgrid-10.json +0 -1
  117. data/set-of-emails/jsonobj/json-sendgrid-11.json +0 -1
  118. data/set-of-emails/jsonobj/json-sendgrid-12.json +0 -1
  119. data/set-of-emails/jsonobj/json-sendgrid-13.json +0 -1
  120. data/set-of-emails/jsonobj/json-sendgrid-14.json +0 -1
  121. data/set-of-emails/jsonobj/json-sendgrid-15.json +0 -1
  122. data/set-of-emails/jsonobj/json-sendgrid-16.json +0 -1
  123. data/set-of-emails/jsonobj/json-sendgrid-17.json +0 -1
@@ -1,19 +1,19 @@
1
- module Sisimai::Bite::Email
2
- # Sisimai::Bite::Email::X2 parses a bounce email which created by Unknown
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/Bite/Email/X2.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/X2.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
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::Bite.smtpagent(self); end
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 scan(mhead, mbody)
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite::Email
2
- # Sisimai::Bite::Email::X3 parses a bounce email which created by Unknown
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/Bite/Email/X3.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/X3.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
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::Bite.smtpagent(self); end
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 scan(mhead, mbody)
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite::Email
2
- # Sisimai::Bite::Email::X4 parses a bounce email which created by some qmail
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/Bite/Email/X4.pm
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/X4.pm
7
7
  # MTA module for qmail clones
8
- require 'sisimai/bite/email'
8
+ require 'sisimai/lhost'
9
9
 
10
- Indicators = Sisimai::Bite::Email.INDICATORS
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::Bite.smtpagent(self); end
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 scan(mhead, mbody)
137
+ def make(mhead, mbody)
138
138
  # Pre process email headers and the body part of the message which generated
139
- # by qmail, see http://cr.yp.to/qmail.html
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite::Email
2
- # Sisimai::Bite::Email::X5 parses a bounce email which created by Unknown
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/Bite/Email/X5.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/X5.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
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::Bite.smtpagent(self); end
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 scan(mhead, mbody)
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite::Email
2
- # Sisimai::Bite::Email::Yahoo parses a bounce email which created by Yahoo!
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/Bite/Email/Yahoo.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/Yahoo.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
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::Bite.smtpagent(self); end
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 scan(mhead, mbody)
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite::Email
2
- # Sisimai::Bite::Email::Yandex parses a bounce email which created by
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/Bite/Email/Yandex.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/Yandex.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
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: http://www.yandex.ru'; end
16
- def smtpagent; return Sisimai::Bite.smtpagent(self); end
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 scan(mhead, mbody)
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite::Email
2
- # Sisimai::Bite::Email::Zoho parses a bounce email which created by Zoho Mail.
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/Bite/Email/Zoho.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/Zoho.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
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::Bite.smtpagent(self); end
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 scan(mhead, mbody)
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::Bite.DELIVERYSTATUS]
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::Bite.DELIVERYSTATUS
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::Bite.DELIVERYSTATUS
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 scan(mhead, mbody)
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
@@ -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::Bite::*::* module
20
+ :ds, # [Array] Parsed data by Sisimai::Lhost::* module
18
21
  :rfc822, # [Hash] Header part of the original message
19
- :catch, # [?] The results returned by hook method
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 = argvs[:input] || 'email'
51
+ input = email.is_a?(Hash) ? 'json' : 'email'
38
52
  field = argvs[:field] || []
39
- child = nil
40
53
 
41
54
  if input == 'email'
42
- # Sisimai::Message::Email
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 = Module.const_get(child).make(methodargv)
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 (Should be implemeneted at each child class)
102
- def make
103
- return {
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
- # This method should be implemented at each child class
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; return []; end
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