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
data/lib/sisimai/data.rb CHANGED
@@ -236,7 +236,7 @@ module Sisimai
236
236
  # Subject: header of the original message
237
237
  p['subject'] = rfc822data['subject'] || ''
238
238
  p['subject'].scrub!('?')
239
- p['subject'].chomp!("\r") if p['subject'].end_with?("\r");
239
+ p['subject'].chomp!("\r") if p['subject'].end_with?("\r")
240
240
 
241
241
  # The value of "List-Id" header
242
242
  p['listid'] = rfc822data['list-id'] || ''
@@ -0,0 +1,101 @@
1
+ module Sisimai
2
+ # Sisimai::Lhost - Base class for Sisimai::Lhost::*
3
+ module Lhost
4
+ class << self
5
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost.pm
6
+ require 'sisimai/rfc5322'
7
+
8
+ # Data structure for parsed bounce messages
9
+ # @return [Hash] Data structure for delivery status
10
+ # @private
11
+ def DELIVERYSTATUS
12
+ return {
13
+ 'spec' => nil, # Protocl specification
14
+ 'date' => nil, # The value of Last-Attempt-Date header
15
+ 'rhost' => nil, # The value of Remote-MTA header
16
+ 'lhost' => nil, # The value of Received-From-MTA header
17
+ 'alias' => nil, # The value of alias entry(RHS)
18
+ 'agent' => nil, # MTA module name
19
+ 'action' => nil, # The value of Action header
20
+ 'status' => nil, # The value of Status header
21
+ 'reason' => nil, # Temporary reason of bounce
22
+ 'command' => nil, # SMTP command in the message body
23
+ 'replycode' => nil, # SMTP Reply code
24
+ 'diagnosis' => nil, # The value of Diagnostic-Code header
25
+ 'recipient' => nil, # The value of Final-Recipient header
26
+ 'softbounce' => nil, # Soft bounce or not
27
+ 'feedbacktype' => nil, # Feedback Type
28
+ }
29
+ end
30
+
31
+ # @abstract Flags for position variable
32
+ # @return [Hash] Position flag data
33
+ # @private
34
+ def INDICATORS
35
+ return {
36
+ :'deliverystatus' => (1 << 1),
37
+ :'message-rfc822' => (1 << 2),
38
+ }
39
+ end
40
+
41
+ def smtpagent(v = '')
42
+ return v.to_s.sub(/\ASisimai::Lhost::/, 'Email::')
43
+ end
44
+ def description; return ''; end
45
+ def headerlist; return []; end
46
+ def removedat; return 'v4.25.5'; end # This method will be removed at the future release of Sisimai
47
+
48
+ # @abstract MTA list
49
+ # @return [Array] MTA list with order
50
+ def index
51
+ return %w[
52
+ Sendmail Postfix Qmail Exim Courier OpenSMTPD Office365 Outlook
53
+ Exchange2007 Exchange2003 Yahoo GSuite Aol SendGrid AmazonSES MailRu
54
+ Yandex MessagingServer Domino Notes ReceivingSES AmazonWorkMail Verizon
55
+ GMX Bigfoot Facebook Zoho EinsUndEins MessageLabs EZweb KDDI Biglobe
56
+ Amavis ApacheJames McAfee MXLogic MailFoundry IMailServer
57
+ MFILTER Activehunter InterScanMSS SurfControl MailMarshalSMTP
58
+ X1 X2 X3 X4 X5 V5sendmail FML Google]
59
+ end
60
+
61
+ # @abstract MTA list which have one or more extra headers
62
+ # @return [Array] MTA list (have extra headers)
63
+ def heads
64
+ return %w[
65
+ Exim Office365 Outlook Exchange2007 Exchange2003 GSuite SendGrid
66
+ AmazonSES ReceivingSES AmazonWorkMail Aol GMX MailRu MessageLabs Yahoo
67
+ Yandex Zoho EinsUndEins MXLogic McAfee MFILTER EZweb Activehunter IMailServer
68
+ SurfControl FML Google
69
+ ]
70
+ end
71
+
72
+ # @abstract Parse bounce messages
73
+ # @param [Hash] mhead Message header of a bounce email
74
+ # @options mhead [String] from From header
75
+ # @options mhead [String] date Date header
76
+ # @options mhead [String] subject Subject header
77
+ # @options mhead [Array] received Received headers
78
+ # @options mhead [String] others Other required headers
79
+ # @param [String] mbody Message body of a bounce email
80
+ # @return [Hash, Nil] Bounce data list and message/rfc822
81
+ # part or nil if it failed to parse or
82
+ # the arguments are missing
83
+ def make; return nil; end
84
+
85
+ # @abstract Print warnings about an obsoleted method. This method will be
86
+ # removed at the future release of Sisimai
87
+ # @until v4.25.5
88
+ def warn(whois = '', useit = nil)
89
+ label = ' ***warning:'
90
+ methodname = caller[0][/`.*'/][1..-2]
91
+ messageset = sprintf("%s %s.%s is marked as obsoleted", label, whois, methodname)
92
+
93
+ useit ||= methodname
94
+ messageset << sprintf(" and will be removed at %s.", removedat)
95
+ messageset << sprintf(" Use %s.%s instead.\n", self.name, useit) if useit != 'gone'
96
+ Kernel.warn messageset
97
+ end
98
+ end
99
+ end
100
+ end
101
+
@@ -1,20 +1,20 @@
1
- module Sisimai::Bite::Email
2
- # Sisimai::Bite::Email::Activehunter parses a bounce email which created
3
- # by TransWARE Active!hunter.
1
+ module Sisimai::Lhost
2
+ # Sisimai::Lhost::Activehunter parses a bounce email which created by
3
+ # TransWARE Active!hunter.
4
4
  # Methods in the module are called from only Sisimai::Message.
5
5
  module Activehunter
6
6
  class << self
7
- # Imported from p5-Sisimail/lib/Sisimai/Bite/Email/Activehunter.pm
8
- require 'sisimai/bite/email'
7
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/Activehunter.pm
8
+ require 'sisimai/lhost'
9
9
 
10
- Indicators = Sisimai::Bite::Email.INDICATORS
10
+ Indicators = Sisimai::Lhost.INDICATORS
11
11
  StartingOf = {
12
12
  message: [' ----- The following addresses had permanent fatal errors -----'],
13
13
  rfc822: ['Content-type: message/rfc822'],
14
14
  }.freeze
15
15
 
16
16
  def description; return 'TransWARE Active!hunter'; end
17
- def smtpagent; return Sisimai::Bite.smtpagent(self); end
17
+ def smtpagent; return Sisimai::Lhost.smtpagent(self); end
18
18
  def headerlist; return %w[x-ahmailid]; end
19
19
 
20
20
  # Parse bounce messages from TransWARE Active!hunter
@@ -28,12 +28,12 @@ module Sisimai::Bite::Email
28
28
  # @return [Hash, Nil] Bounce data list and message/rfc822
29
29
  # part or nil if it failed to parse or
30
30
  # the arguments are missing
31
- def scan(mhead, mbody)
31
+ def make(mhead, mbody)
32
32
  # :from => %r/\A"MAILER-DAEMON"/,
33
33
  # :subject => %r/FAILURE NOTICE :/,
34
34
  return nil unless mhead['x-ahmailid']
35
35
 
36
- dscontents = [Sisimai::Bite.DELIVERYSTATUS]
36
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
37
37
  hasdivided = mbody.split("\n")
38
38
  rfc822list = [] # (Array) Each line in message/rfc822 part string
39
39
  blanklines = 0 # (Integer) The number of blank lines
@@ -83,7 +83,7 @@ module Sisimai::Bite::Email
83
83
  # >>> kijitora@example.org <kijitora@example.org>
84
84
  if v['recipient']
85
85
  # There are multiple recipient addresses in the message body.
86
- dscontents << Sisimai::Bite.DELIVERYSTATUS
86
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
87
87
  v = dscontents[-1]
88
88
  end
89
89
  v['recipient'] = cv[1]
@@ -1,19 +1,19 @@
1
- module Sisimai::Bite::Email
2
- # Sisimai::Bite::Email::Amavis parses a bounce email which created by
1
+ module Sisimai::Lhost
2
+ # Sisimai::Lhost::Amavis parses a bounce email which created by
3
3
  # amavsid-new. Methods in the module are called from only Sisimai::Message.
4
4
  module Amavis
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Bite/Email/Amavis.pm
7
- require 'sisimai/bite/email'
6
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/Amavis.pm
7
+ require 'sisimai/lhost'
8
8
 
9
- Indicators = Sisimai::Bite::Email.INDICATORS
9
+ Indicators = Sisimai::Lhost.INDICATORS
10
10
  StartingOf = {
11
11
  message: ['The message '],
12
12
  rfc822: ['Content-Type: text/rfc822-headers'],
13
13
  }.freeze
14
14
 
15
15
  def description; return 'amavisd-new: https://www.amavis.org/'; end
16
- def smtpagent; return Sisimai::Bite.smtpagent(self); end
16
+ def smtpagent; return Sisimai::Lhost.smtpagent(self); end
17
17
 
18
18
  # Parse bounce messages from amavisd-new
19
19
  # @param [Hash] mhead Message headers of a bounce email
@@ -26,7 +26,7 @@ module Sisimai::Bite::Email
26
26
  # @return [Hash, Nil] Bounce data list and message/rfc822
27
27
  # part or nil if it failed to parse or
28
28
  # the arguments are missing
29
- def scan(mhead, mbody)
29
+ def make(mhead, mbody)
30
30
  # From: "Content-filter at neko1.example.jp" <postmaster@neko1.example.jp>
31
31
  # Subject: Undeliverable mail, MTA-BLOCKED
32
32
  return nil unless mhead['from'].to_s.start_with?('"Content-filter at ')
@@ -35,7 +35,7 @@ module Sisimai::Bite::Email
35
35
  fieldtable = Sisimai::RFC1894.FIELDTABLE
36
36
  permessage = {} # (Hash) Store values of each Per-Message field
37
37
 
38
- dscontents = [Sisimai::Bite.DELIVERYSTATUS]
38
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
39
39
  hasdivided = mbody.split("\n")
40
40
  havepassed = ['']
41
41
  rfc822list = [] # (Array) Each line in message/rfc822 part string
@@ -90,7 +90,7 @@ module Sisimai::Bite::Email
90
90
  # Final-Recipient: rfc822; kijitora@example.jp
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'] = o[2]
@@ -0,0 +1,403 @@
1
+ module Sisimai::Lhost
2
+ # Sisimai::Lhost::AmazonSES parses a bounce email which created by
3
+ # Amazon Simple Email Service.
4
+ # Methods in the module are called from only Sisimai::Message.
5
+ module AmazonSES
6
+ class << self
7
+ # Imported from p5-Sisimail/lib/Sisimai/Lhost/AmazonSES.pm
8
+ require 'sisimai/lhost'
9
+
10
+ # https://aws.amazon.com/ses/
11
+ Indicators = Sisimai::Lhost.INDICATORS
12
+ StartingOf = {
13
+ message: ['The following message to <', 'An error occurred while trying to deliver the mail'],
14
+ rfc822: ['content-type: message/rfc822'],
15
+ }.freeze
16
+ MessagesOf = { 'expired' => ['Delivery expired'] }.freeze
17
+
18
+ def description; return 'Amazon SES(Sending): https://aws.amazon.com/ses/'; end
19
+ def smtpagent; return Sisimai::Lhost.smtpagent(self); end
20
+
21
+ # X-SenderID: Sendmail Sender-ID Filter v1.0.0 nijo.example.jp p7V3i843003008
22
+ # X-Original-To: 000001321defbd2a-788e31c8-2be1-422f-a8d4-cf7765cc9ed7-000000@email-bounces.amazonses.com
23
+ # X-AWS-Outgoing: 199.255.192.156
24
+ # X-SES-Outgoing: 2016.10.12-54.240.27.6
25
+ def headerlist; return %w[x-aws-outgoing x-ses-outgoing x-amz-sns-message-id]; end
26
+
27
+ # Parse bounce messages from Amazon SES
28
+ # @param [Hash] mhead Message headers of a bounce email
29
+ # @options mhead [String] from From header
30
+ # @options mhead [String] date Date header
31
+ # @options mhead [String] subject Subject header
32
+ # @options mhead [Array] received Received headers
33
+ # @options mhead [String] others Other required headers
34
+ # @param [String] mbody Message body of a bounce email
35
+ # @return [Hash, Nil] Bounce data list and message/rfc822
36
+ # part or nil if it failed to parse or
37
+ # the arguments are missing
38
+ def make(mhead, mbody)
39
+ if mbody.start_with?('{')
40
+ # The message body is JSON string
41
+ return nil unless mhead['x-amz-sns-message-id']
42
+ return nil if mhead['x-amz-sns-message-id'].empty?
43
+
44
+ hasdivided = mbody.split("\n")
45
+ jsonstring = ''
46
+ sespayload = nil
47
+ foldedline = false
48
+
49
+ while e = hasdivided.shift do
50
+ # Find JSON string from the message body
51
+ next if e.empty?
52
+ break if e == '--'
53
+ break if e == '__END_OF_EMAIL_MESSAGE__'
54
+
55
+ # The line starts with " ", continued from !\n.
56
+ e.lstrip! if foldedline
57
+ foldedline = false
58
+
59
+ if e.end_with?('!')
60
+ # ... long long line ...![\n]
61
+ e.chomp!('!')
62
+ foldedline = true
63
+ end
64
+ jsonstring << e
65
+ end
66
+
67
+ begin
68
+ if RUBY_PLATFORM.start_with?('java')
69
+ # java-based ruby environment like JRuby.
70
+ require 'jrjackson'
71
+ jsonobject = JrJackson::Json.load(jsonstring)
72
+
73
+ # 'Message' => '{"notificationType":"Bounce",...
74
+ sespayload = JrJackson::Json.load(jsonobject['Message']) if jsonobject['Message']
75
+ else
76
+ # Matz' Ruby Implementation
77
+ require 'oj'
78
+ jsonobject = Oj.load(jsonstring)
79
+
80
+ # 'Message' => '{"notificationType":"Bounce",...
81
+ sespayload = Oj.load(jsonobject['Message']) if jsonobject['Message']
82
+ end
83
+ sespayload ||= jsonobject
84
+
85
+ rescue StandardError => ce
86
+ # Something wrong in decoding JSON
87
+ warn ' ***warning: Failed to decode JSON: ' << ce.to_s
88
+ return nil
89
+ end
90
+
91
+ return json(sespayload)
92
+ else
93
+ # The message body is an email
94
+
95
+ # :from => %r/\AMAILER-DAEMON[@]email[-]bounces[.]amazonses[.]com\z/,
96
+ # :subject => %r/\ADelivery Status Notification [(]Failure[)]\z/,
97
+ return nil if mhead['x-mailer'].to_s.start_with?('Amazon WorkMail')
98
+
99
+ match = 0
100
+ match += 1 if mhead['x-aws-outgoing']
101
+ match += 1 if mhead['x-ses-outgoing']
102
+ return nil unless match > 0
103
+
104
+ require 'sisimai/rfc1894'
105
+ fieldtable = Sisimai::RFC1894.FIELDTABLE
106
+ permessage = {} # (Hash) Store values of each Per-Message field
107
+
108
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
109
+ hasdivided = mbody.split("\n")
110
+ havepassed = ['']
111
+ rfc822list = [] # (Array) Each line in message/rfc822 part string
112
+ blanklines = 0 # (Integer) The number of blank lines
113
+ readcursor = 0 # (Integer) Points the current cursor position
114
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
115
+ v = nil
116
+
117
+ while e = hasdivided.shift do
118
+ # Save the current line for the next loop
119
+ havepassed << e
120
+ p = havepassed[-2]
121
+
122
+ if readcursor == 0
123
+ # Beginning of the bounce message or message/delivery-status part
124
+ if e.start_with?(StartingOf[:message][0], StartingOf[:message][1])
125
+ readcursor |= Indicators[:deliverystatus]
126
+ next
127
+ end
128
+ end
129
+
130
+ if (readcursor & Indicators[:'message-rfc822']) == 0
131
+ # Beginning of the original message part(message/rfc822)
132
+ if e == StartingOf[:rfc822][0]
133
+ readcursor |= Indicators[:'message-rfc822']
134
+ next
135
+ end
136
+ end
137
+
138
+ if readcursor & Indicators[:'message-rfc822'] > 0
139
+ # message/rfc822 OR text/rfc822-headers part
140
+ if e.empty?
141
+ blanklines += 1
142
+ break if blanklines > 1
143
+ next
144
+ end
145
+ rfc822list << e
146
+ else
147
+ # message/delivery-status part
148
+ next if (readcursor & Indicators[:deliverystatus]) == 0
149
+ next if e.empty?
150
+
151
+ if f = Sisimai::RFC1894.match(e)
152
+ # "e" matched with any field defined in RFC3464
153
+ next unless o = Sisimai::RFC1894.field(e)
154
+ v = dscontents[-1]
155
+
156
+ if o[-1] == 'addr'
157
+ # Final-Recipient: rfc822; kijitora@example.jp
158
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
159
+ if o[0] == 'final-recipient'
160
+ # Final-Recipient: rfc822; kijitora@example.jp
161
+ if v['recipient']
162
+ # There are multiple recipient addresses in the message body.
163
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
164
+ v = dscontents[-1]
165
+ end
166
+ v['recipient'] = o[2]
167
+ recipients += 1
168
+ else
169
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
170
+ v['alias'] = o[2]
171
+ end
172
+ elsif o[-1] == 'code'
173
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
174
+ v['spec'] = o[1]
175
+ v['diagnosis'] = o[2]
176
+ else
177
+ # Other DSN fields defined in RFC3464
178
+ next unless fieldtable.key?(o[0])
179
+ v[fieldtable[o[0]]] = o[2]
180
+
181
+ next unless f == 1
182
+ permessage[fieldtable[o[0]]] = o[2]
183
+ end
184
+ else
185
+ # Continued line of the value of Diagnostic-Code field
186
+ next unless p.start_with?('Diagnostic-Code:')
187
+ next unless cv = e.match(/\A[ \t]+(.+)\z/)
188
+ v['diagnosis'] << ' ' << cv[1]
189
+ havepassed[-1] = 'Diagnostic-Code: ' << e
190
+ end
191
+ end # End of message/delivery-status
192
+ end
193
+
194
+ if recipients == 0 && mbody =~ /notificationType/
195
+ # Try to parse with Sisimai::Lhost::AmazonSES module
196
+ j = Sisimai::Lhost::AmazonSES.json(mhead, mbody)
197
+
198
+ if j['ds'].is_a? Array
199
+ # Update dscontents
200
+ dscontents = j['ds']
201
+ recipients = j['ds'].size
202
+ end
203
+ end
204
+ return nil unless recipients > 0
205
+
206
+ dscontents.each do |e|
207
+ # Set default values if each value is empty.
208
+ e['lhost'] ||= permessage['rhost']
209
+ permessage.each_key { |a| e[a] ||= permessage[a] || '' }
210
+
211
+ e['agent'] = self.smtpagent
212
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].to_s.tr("\n", ' '))
213
+
214
+ if e['status'].to_s.start_with?('5.0.0', '5.1.0', '4.0.0', '4.1.0')
215
+ # Get other D.S.N. value from the error message
216
+ errormessage = e['diagnosis']
217
+
218
+ if cv = e['diagnosis'].match(/["'](\d[.]\d[.]\d.+)['"]/)
219
+ # 5.1.0 - Unknown address error 550-'5.7.1 ...
220
+ errormessage = cv[1]
221
+ end
222
+ e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status']
223
+ end
224
+
225
+ MessagesOf.each_key do |r|
226
+ # Verify each regular expression of session errors
227
+ next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) }
228
+ e['reason'] = r
229
+ break
230
+ end
231
+
232
+ end
233
+
234
+ rfc822part = Sisimai::RFC5322.weedout(rfc822list)
235
+ return { 'ds' => dscontents, 'rfc822' => rfc822part }
236
+ end # END of a parser for email message
237
+ end # END of def make()
238
+
239
+ # @abstract Adapt Amazon SES bounce object for Sisimai::Message format
240
+ # @param [Hash] argvs bounce object(JSON) retrieved from Amazon SNS
241
+ # @return [Hash, Nil] Bounce data list and message/rfc822 part or
242
+ # nil if it failed to parse or the
243
+ # arguments are missing
244
+ # @since v4.20.0
245
+ # @until v4.25.5
246
+ def json(argvs)
247
+ return nil unless argvs.is_a? Hash
248
+ return nil if argvs.empty?
249
+ return nil unless argvs.key?('notificationType')
250
+
251
+ # https://docs.aws.amazon.com/en_us/ses/latest/DeveloperGuide/notification-contents.html
252
+ bouncetype = {
253
+ 'Permanent' => {
254
+ 'General' => '',
255
+ 'NoEmail' => '',
256
+ 'Suppressed' => '',
257
+ },
258
+ 'Transient' => {
259
+ 'General' => '',
260
+ 'MailboxFull' => 'mailboxfull',
261
+ 'MessageTooLarge' => 'mesgtoobig',
262
+ 'ContentRejected' => '',
263
+ 'AttachmentRejected' => '',
264
+ },
265
+ }.freeze
266
+
267
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
268
+ rfc822head = {} # (Hash) Check flags for headers in RFC822 part
269
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
270
+ labeltable = {
271
+ 'Bounce' => 'bouncedRecipients',
272
+ 'Complaint' => 'complainedRecipients',
273
+ }
274
+ v = nil
275
+
276
+ if %w[Bounce Complaint].index(argvs['notificationType'])
277
+ # { "notificationType":"Bounce", "bounce": { "bounceType":"Permanent",...
278
+ o = argvs[argvs['notificationType'].downcase].dup
279
+ r = o[labeltable[argvs['notificationType']]] || []
280
+
281
+ while e = r.shift do
282
+ # 'bouncedRecipients' => [ { 'emailAddress' => 'bounce@si...' }, ... ]
283
+ # 'complainedRecipients' => [ { 'emailAddress' => 'complaint@si...' }, ... ]
284
+ next unless Sisimai::RFC5322.is_emailaddress(e['emailAddress'])
285
+
286
+ v = dscontents[-1]
287
+ if v['recipient']
288
+ # There are multiple recipient addresses in the message body.
289
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
290
+ v = dscontents[-1]
291
+ end
292
+ recipients += 1
293
+ v['recipient'] = e['emailAddress']
294
+
295
+ if argvs['notificationType'] == 'Bounce'
296
+ # 'bouncedRecipients => [ {
297
+ # 'emailAddress' => 'bounce@simulator.amazonses.com',
298
+ # 'action' => 'failed',
299
+ # 'status' => '5.1.1',
300
+ # 'diagnosticCode' => 'smtp; 550 5.1.1 user unknown'
301
+ # }, ... ]
302
+ v['action'] = e['action']
303
+ v['status'] = e['status']
304
+
305
+ if cv = e['diagnosticCode'].match(/\A(.+?);[ ]*(.+)\z/)
306
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
307
+ v['spec'] = cv[1].upcase
308
+ v['diagnosis'] = cv[2]
309
+ else
310
+ v['diagnosis'] = e['diagnosticCode']
311
+ end
312
+
313
+ # 'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
314
+ if cv = o['reportingMTA'].match(/\Adsn;[ ](.+)\z/) then v['lhost'] = cv[1] end
315
+
316
+ if bouncetype.key?(o['bounceType']) &&
317
+ bouncetype[o['bounceType']].key?(o['bounceSubType'])
318
+ # 'bounce' => {
319
+ # 'bounceType' => 'Permanent',
320
+ # 'bounceSubType' => 'General'
321
+ # },
322
+ v['reason'] = bouncetype[o['bounceType']][o['bounceSubType']]
323
+ end
324
+ else
325
+ # 'complainedRecipients' => [ {
326
+ # 'emailAddress' => 'complaint@simulator.amazonses.com' }, ... ],
327
+ v['reason'] = 'feedback'
328
+ v['feedbacktype'] = o['complaintFeedbackType'] || ''
329
+ end
330
+
331
+ v['date'] = o['timestamp'] || argvs['mail']['timestamp']
332
+ v['date'].sub!(/[.]\d+Z\z/, '')
333
+ end
334
+ elsif argvs['notificationType'] == 'Delivery'
335
+ # { "notificationType":"Delivery", "delivery": { ...
336
+ o = argvs['delivery'].dup
337
+ r = o['recipients'] || []
338
+
339
+ while e = r.shift do
340
+ # 'delivery' => {
341
+ # 'timestamp' => '2016-11-23T12:01:03.512Z',
342
+ # 'processingTimeMillis' => 3982,
343
+ # 'reportingMTA' => 'a27-29.smtp-out.us-west-2.amazonses.com',
344
+ # 'recipients' => [
345
+ # 'success@simulator.amazonses.com'
346
+ # ],
347
+ # 'smtpResponse' => '250 2.6.0 Message received'
348
+ # },
349
+ next unless Sisimai::RFC5322.is_emailaddress(e)
350
+
351
+ v = dscontents[-1]
352
+ if v['recipient']
353
+ # There are multiple recipient addresses in the message body.
354
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
355
+ v = dscontents[-1]
356
+ end
357
+ recipients += 1
358
+ v['recipient'] = e
359
+ v['lhost'] = o['reportingMTA'] || ''
360
+ v['diagnosis'] = o['smtpResponse'] || ''
361
+ v['status'] = Sisimai::SMTP::Status.find(v['diagnosis']) || ''
362
+ v['replycode'] = Sisimai::SMTP::Reply.find(v['diagnosis']) || ''
363
+ v['reason'] = 'delivered'
364
+ v['action'] = 'deliverable'
365
+
366
+ v['date'] = o['timestamp'] || argvs['mail']['timestamp']
367
+ v['date'].sub!(/[.]\d+Z\z/, '')
368
+ end
369
+ else
370
+ # The value of "notificationType" is not any of "Bounce", "Complaint",
371
+ # or "Delivery".
372
+ return nil
373
+ end
374
+ return nil unless recipients > 0
375
+
376
+ dscontents.each do |e|
377
+ e['agent'] = self.smtpagent
378
+ end
379
+
380
+ if argvs['mail']['headers']
381
+ # "headersTruncated":false,
382
+ # "headers":[ { ...
383
+ argvs['mail']['headers'].each do |e|
384
+ # 'headers' => [ { 'name' => 'From', 'value' => 'neko@nyaan.jp' }, ... ],
385
+ next unless %w[From To Subject Message-ID Date].index(e['name'])
386
+ rfc822head[e['name'].downcase] = e['value']
387
+ end
388
+ end
389
+
390
+ unless rfc822head['message-id']
391
+ # Try to get the value of "Message-Id".
392
+ if argvs['mail']['messageId']
393
+ # 'messageId' => '01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000'
394
+ rfc822head['message-id'] = argvs['mail']['messageId']
395
+ end
396
+ end
397
+ return { 'ds' => dscontents, 'rfc822' => rfc822head }
398
+ end
399
+
400
+ end
401
+ end
402
+ end
403
+