sisimai 4.14.2 → 4.15.0

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -2
  3. data/.travis.yml +0 -1
  4. data/Changes +16 -0
  5. data/README.md +69 -17
  6. data/appveyor.yml +23 -0
  7. data/lib/sisimai.rb +9 -7
  8. data/lib/sisimai/address.rb +8 -2
  9. data/lib/sisimai/arf.rb +1 -6
  10. data/lib/sisimai/data.rb +35 -47
  11. data/lib/sisimai/data/json.rb +16 -12
  12. data/lib/sisimai/datetime.rb +1 -8
  13. data/lib/sisimai/mail/maildir.rb +0 -3
  14. data/lib/sisimai/mail/mbox.rb +1 -1
  15. data/lib/sisimai/mail/stdin.rb +2 -1
  16. data/lib/sisimai/message.rb +46 -31
  17. data/lib/sisimai/mime.rb +2 -2
  18. data/lib/sisimai/msp.rb +3 -3
  19. data/lib/sisimai/msp/de/einsundeins.rb +1 -1
  20. data/lib/sisimai/msp/de/gmx.rb +1 -1
  21. data/lib/sisimai/msp/jp/biglobe.rb +2 -2
  22. data/lib/sisimai/msp/jp/ezweb.rb +10 -10
  23. data/lib/sisimai/msp/jp/kddi.rb +1 -1
  24. data/lib/sisimai/msp/ru/yandex.rb +1 -1
  25. data/lib/sisimai/msp/uk/messagelabs.rb +1 -1
  26. data/lib/sisimai/msp/us/amazonses.rb +1 -1
  27. data/lib/sisimai/msp/us/amazonworkmail.rb +224 -0
  28. data/lib/sisimai/msp/us/aol.rb +1 -1
  29. data/lib/sisimai/msp/us/bigfoot.rb +1 -1
  30. data/lib/sisimai/msp/us/facebook.rb +1 -1
  31. data/lib/sisimai/msp/us/google.rb +1 -1
  32. data/lib/sisimai/msp/us/office365.rb +273 -0
  33. data/lib/sisimai/msp/us/outlook.rb +2 -3
  34. data/lib/sisimai/msp/us/receivingses.rb +1 -1
  35. data/lib/sisimai/msp/us/sendgrid.rb +2 -2
  36. data/lib/sisimai/msp/us/verizon.rb +7 -7
  37. data/lib/sisimai/msp/us/yahoo.rb +1 -1
  38. data/lib/sisimai/msp/us/zoho.rb +1 -1
  39. data/lib/sisimai/mta.rb +8 -9
  40. data/lib/sisimai/mta/activehunter.rb +1 -1
  41. data/lib/sisimai/mta/apachejames.rb +1 -1
  42. data/lib/sisimai/mta/courier.rb +1 -1
  43. data/lib/sisimai/mta/domino.rb +1 -1
  44. data/lib/sisimai/mta/exchange.rb +15 -15
  45. data/lib/sisimai/mta/imailserver.rb +1 -1
  46. data/lib/sisimai/mta/interscanmss.rb +1 -1
  47. data/lib/sisimai/mta/mailfoundry.rb +1 -1
  48. data/lib/sisimai/mta/mailmarshalsmtp.rb +1 -1
  49. data/lib/sisimai/mta/mcafee.rb +1 -1
  50. data/lib/sisimai/mta/messagingserver.rb +1 -1
  51. data/lib/sisimai/mta/mxlogic.rb +2 -2
  52. data/lib/sisimai/mta/notes.rb +1 -1
  53. data/lib/sisimai/mta/opensmtpd.rb +1 -1
  54. data/lib/sisimai/mta/postfix.rb +2 -2
  55. data/lib/sisimai/mta/qmail.rb +1 -1
  56. data/lib/sisimai/mta/sendmail.rb +2 -2
  57. data/lib/sisimai/mta/surfcontrol.rb +1 -1
  58. data/lib/sisimai/mta/v5sendmail.rb +1 -1
  59. data/lib/sisimai/mta/x1.rb +1 -1
  60. data/lib/sisimai/mta/x2.rb +1 -1
  61. data/lib/sisimai/mta/x3.rb +1 -1
  62. data/lib/sisimai/mta/x4.rb +1 -1
  63. data/lib/sisimai/mta/x5.rb +1 -1
  64. data/lib/sisimai/order.rb +4 -1
  65. data/lib/sisimai/reason.rb +15 -18
  66. data/lib/sisimai/reason/hostunknown.rb +5 -0
  67. data/lib/sisimai/reason/mesgtoobig.rb +5 -0
  68. data/lib/sisimai/reason/userunknown.rb +1 -1
  69. data/lib/sisimai/rfc3464.rb +3 -3
  70. data/lib/sisimai/rfc3834.rb +1 -1
  71. data/lib/sisimai/time.rb +1 -1
  72. data/lib/sisimai/version.rb +1 -1
  73. data/set-of-emails/maildir/bsd/us-amazonworkmail-01.eml +156 -0
  74. data/set-of-emails/maildir/bsd/us-amazonworkmail-02.eml +160 -0
  75. data/set-of-emails/maildir/bsd/us-amazonworkmail-03.eml +161 -0
  76. data/set-of-emails/maildir/bsd/us-amazonworkmail-04.eml +156 -0
  77. data/set-of-emails/maildir/bsd/us-amazonworkmail-05.eml +157 -0
  78. data/set-of-emails/maildir/bsd/us-office365-01.eml +102 -0
  79. data/sisimai.gemspec +7 -0
  80. metadata +26 -3
@@ -0,0 +1,224 @@
1
+ module Sisimai
2
+ module MSP::US
3
+ # Sisimai::MSP::US::AmazonWorkMail parses a bounce email which created by
4
+ # Amazon WorkMail. Methods in the module are called from only Sisimai::Message.
5
+ module AmazonWorkMail
6
+ # Imported from p5-Sisimail/lib/Sisimai/MSP/US/AmazonWorkMail.pm
7
+ class << self
8
+ require 'sisimai/msp'
9
+ require 'sisimai/rfc5322'
10
+
11
+ # https://aws.amazon.com/workmail/
12
+ Re0 = {
13
+ :'subject' => %r/Delivery[_ ]Status[_ ]Notification[_ ].+Failure/,
14
+ :'received' => %r/.+[.]smtp-out[.].+[.]amazonses[.]com\b/,
15
+ :'x-mailer' => %r/\AAmazon WorkMail\z/,
16
+ }
17
+ Re1 = {
18
+ :begin => %r/\ATechnical report:\z/,
19
+ :rfc822 => %r|\Acontent-type: message/rfc822\z|,
20
+ :endof => %r/\A__END_OF_EMAIL_MESSAGE__\z/,
21
+ }
22
+ Indicators = Sisimai::MSP.INDICATORS
23
+ LongFields = Sisimai::RFC5322.LONGFIELDS
24
+ RFC822Head = Sisimai::RFC5322.HEADERFIELDS
25
+
26
+ def description; return 'Amazon WorkMail: https://aws.amazon.com/workmail/'; end
27
+ def smtpagent; return 'US::AmazonWorkMail'; end
28
+
29
+ # X-Mailer: Amazon WorkMail
30
+ # X-Original-Mailer: Amazon WorkMail
31
+ # X-Ses-Outgoing: 2016.01.14-54.240.27.159
32
+ def headerlist; return ['X-SES-Outgoing', 'X-Original-Mailer']; end
33
+ def pattern; return Re0; end
34
+
35
+ # Parse bounce messages from Amazon WorkMail
36
+ # @param [Hash] mhead Message header of a bounce email
37
+ # @options mhead [String] from From header
38
+ # @options mhead [String] date Date header
39
+ # @options mhead [String] subject Subject header
40
+ # @options mhead [Array] received Received headers
41
+ # @options mhead [String] others Other required headers
42
+ # @param [String] mbody Message body of a bounce email
43
+ # @return [Hash, Nil] Bounce data list and message/rfc822
44
+ # part or nil if it failed to parse or
45
+ # the arguments are missing
46
+ def scan(mhead, mbody)
47
+ return nil unless mhead
48
+ return nil unless mbody
49
+
50
+ match = 0
51
+ xmail = mhead['x-original-mailer'] || mhead['x-mailer'] || ''
52
+
53
+ match += 1 if mhead['x-ses-outgoing']
54
+ unless xmail.empty?
55
+ # X-Mailer: Amazon WorkMail
56
+ # X-Original-Mailer: Amazon WorkMail
57
+ match += 1 if xmail =~ Re0[:'x-mailer']
58
+ end
59
+ return nil if match < 2
60
+
61
+ if mbody =~ /^Content-Transfer-Encoding: quoted-printable$/
62
+ # This is a multi-part message in MIME format. Your mail reader does not
63
+ # understand MIME message format.
64
+ # --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ
65
+ # Content-Type: text/plain; charset=iso-8859-15
66
+ # Content-Transfer-Encoding: quoted-printable
67
+ require 'sisimai/mime'
68
+ mbody = mbody.sub(/\A.+?quoted-printable/ms, '')
69
+ mbody = Sisimai::MIME.qprintd(mbody)
70
+ end
71
+
72
+ dscontents = []; dscontents << Sisimai::MSP.DELIVERYSTATUS
73
+ hasdivided = mbody.split("\n")
74
+ rfc822next = { 'from' => false, 'to' => false, 'subject' => false }
75
+ rfc822part = '' # (String) message/rfc822-headers part
76
+ previousfn = '' # (String) Previous field name
77
+ readcursor = 0 # (Integer) Points the current cursor position
78
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
79
+ connvalues = 0 # (Integer) Flag, 1 if all the value of $connheader have been set
80
+ connheader = {
81
+ 'lhost' => '', # The value of Reporting-MTA header
82
+ }
83
+ v = nil
84
+
85
+ hasdivided.each do |e|
86
+ if readcursor == 0
87
+ # Beginning of the bounce message or delivery status part
88
+ if e =~ Re1[:begin]
89
+ readcursor |= Indicators[:'deliverystatus']
90
+ next
91
+ end
92
+ end
93
+
94
+ if readcursor & Indicators[:'message-rfc822'] == 0
95
+ # Beginning of the original message part
96
+ if e =~ Re1[:rfc822]
97
+ readcursor |= Indicators[:'message-rfc822']
98
+ next
99
+ end
100
+ end
101
+
102
+ if readcursor & Indicators[:'message-rfc822'] > 0
103
+ # After "message/rfc822"
104
+ if cv = e.match(/\A([-0-9A-Za-z]+?)[:][ ]*.+\z/)
105
+ # Get required headers only
106
+ lhs = cv[1].downcase
107
+ previousfn = ''
108
+ next unless RFC822Head.key?(lhs)
109
+
110
+ previousfn = lhs
111
+ rfc822part += e + "\n"
112
+
113
+ elsif e =~ /\A[ \t]+/
114
+ # Continued line from the previous line
115
+ next if rfc822next[previousfn]
116
+ rfc822part += e + "\n" if LongFields.key?(previousfn)
117
+
118
+ else
119
+ # Check the end of headers in rfc822 part
120
+ next unless LongFields.key?(previousfn)
121
+ next unless e.empty?
122
+ rfc822next[previousfn] = true
123
+ end
124
+
125
+ else
126
+ # Before "message/rfc822"
127
+ next if readcursor & Indicators[:'deliverystatus'] == 0
128
+ next if e.empty?
129
+
130
+ if connvalues == connheader.keys.size
131
+ # Action: failed
132
+ # Final-Recipient: rfc822; kijitora@libsisimai.org
133
+ # Diagnostic-Code: smtp; 554 4.4.7 Message expired: unable to deliver in 840 minutes.<421 4.4.2 Connection timed out>
134
+ # Status: 4.4.7
135
+ v = dscontents[-1]
136
+
137
+ if cv = e.match(/\A[Ff]inal-[Rr]ecipient:[ ]*(?:RFC|rfc)822;[ ]*([^ ]+)\z/)
138
+ # Final-Recipient: RFC822; kijitora@example.jp
139
+ if v['recipient']
140
+ # There are multiple recipient addresses in the message body.
141
+ dscontents << Sisimai::MSP.DELIVERYSTATUS
142
+ v = dscontents[-1]
143
+ end
144
+ v['recipient'] = cv[1]
145
+ recipients += 1
146
+
147
+ elsif cv = e.match(/\A[Aa]ction:[ ]*(.+)\z/)
148
+ # Action: failed
149
+ v['action'] = cv[1].downcase
150
+
151
+ elsif cv = e.match(/\A[Ss]tatus:[ ]*(\d[.]\d+[.]\d+)/)
152
+ # Status: 5.1.1
153
+ v['status'] = cv[1]
154
+
155
+ else
156
+ if cv = e.match(/\A[Dd]iagnostic-[Cc]ode:[ ]*(.+?);[ ]*(.+)\z/)
157
+ # Diagnostic-Code: SMTP; 550 5.1.1 <kijitora@example.jp>... User Unknown
158
+ v['spec'] = cv[1].upcase
159
+ v['diagnosis'] = cv[2]
160
+ end
161
+ end
162
+ else
163
+ # Technical report:
164
+ #
165
+ # Reporting-MTA: dsn; a27-85.smtp-out.us-west-2.amazonses.com
166
+ #
167
+ if cv = e.match(/\A[Rr]eporting-MTA:[ ]*[DNSdns]+;[ ]*(.+)\z/)
168
+ # Reporting-MTA: dns; mx.example.jp
169
+ next if connheader['lhost'].size > 0
170
+ connheader['lhost'] = cv[1].downcase
171
+ connvalues += 1
172
+ end
173
+ end
174
+
175
+ # <!DOCTYPE HTML><html>
176
+ # <head>
177
+ # <meta name="Generator" content="Amazon WorkMail v3.0-2023.77">
178
+ # <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
179
+ break if e =~ /\A[<]!DOCTYPE HTML[>][<]html[>]\z/
180
+ end
181
+ end
182
+
183
+ return nil if recipients == 0
184
+ require 'sisimai/string'
185
+ require 'sisimai/smtp/status'
186
+
187
+ dscontents.map do |e|
188
+ # Set default values if each value is empty.
189
+ connheader.each_key { |a| e[a] ||= connheader[a] || '' }
190
+
191
+ if mhead['received'].size > 0
192
+ # Get localhost and remote host name from Received header.
193
+ r0 = mhead['received']
194
+ %w|lhost rhost|.each { |a| e[a] ||= '' }
195
+ e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
196
+ e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
197
+ end
198
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
199
+
200
+ if e['status'] =~ /\A[45][.][01][.]0\z/
201
+ # Get other D.S.N. value from the error message
202
+ errormessage = e['diagnosis']
203
+
204
+ if cv = e['diagnosis'].match(/["'](\d[.]\d[.]\d.+)['"]/)
205
+ # 5.1.0 - Unknown address error 550-'5.7.1 ...
206
+ errormessage = cv[1]
207
+ end
208
+ pseudostatus = Sisimai::SMTP::Status.find(errormessage)
209
+ e['status'] = pseudostatus if pseudostatus.size > 0
210
+ end
211
+
212
+ e['reason'] ||= Sisimai::SMTP::Status.name(e['status'])
213
+ e['spec'] ||= 'SMTP'
214
+ e['agent'] = Sisimai::MSP::US::AmazonWorkMail.smtpagent
215
+ end
216
+
217
+ return { 'ds' => dscontents, 'rfc822' => rfc822part }
218
+ end
219
+
220
+ end
221
+ end
222
+ end
223
+ end
224
+
@@ -201,7 +201,7 @@ module Sisimai
201
201
  if mhead['received'].size > 0
202
202
  # Get localhost and remote host name from Received header.
203
203
  r0 = mhead['received']
204
- ['lhost', 'rhost'].each { |a| e[a] ||= '' }
204
+ %w|lhost rhost|.each { |a| e[a] ||= '' }
205
205
  e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
206
206
  e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
207
207
  end
@@ -212,7 +212,7 @@ module Sisimai
212
212
  if mhead['received'].size > 0
213
213
  # Get localhost and remote host name from Received header.
214
214
  r0 = mhead['received']
215
- ['lhost', 'rhost'].each { |a| e[a] ||= '' }
215
+ %w|lhost rhost|.each { |a| e[a] ||= '' }
216
216
  e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
217
217
  e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
218
218
  end
@@ -245,7 +245,7 @@ module Sisimai
245
245
  if mhead['received'].size > 0
246
246
  # Get localhost and remote host name from Received header.
247
247
  r0 = mhead['received']
248
- ['lhost', 'rhost'].each { |a| e[a] ||= '' }
248
+ %w|lhost rhost|.each { |a| e[a] ||= '' }
249
249
  e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
250
250
  e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
251
251
  end
@@ -311,7 +311,7 @@ module Sisimai
311
311
  if mhead['received'].size > 0
312
312
  # Get localhost and remote host name from Received header.
313
313
  r0 = mhead['received']
314
- ['lhost', 'rhost'].each { |a| e[a] ||= '' }
314
+ %w|lhost rhost|.each { |a| e[a] ||= '' }
315
315
  e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
316
316
  e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
317
317
  end
@@ -0,0 +1,273 @@
1
+ module Sisimai
2
+ module MSP::US
3
+ # Sisimai::MSP::US::Office365 parses a bounce email which created by Microsoft
4
+ # Office 365. Methods in the module are called from only Sisimai::Message.
5
+ module Office365
6
+ # Imported from p5-Sisimail/lib/Sisimai/MSP/US/Office365.pm
7
+ class << self
8
+ require 'sisimai/msp'
9
+ require 'sisimai/rfc5322'
10
+
11
+ Re0 = {
12
+ :subject => %r/Undeliverable:/,
13
+ :received => %r/.+[.](?:outbound[.]protection|prod)[.]outlook[.]com\b/,
14
+ }
15
+ Re1 = {
16
+ :begin => %r{\A(?:
17
+ Delivery[ ]has[ ]failed[ ]to[ ]these[ ]recipients[ ]or[ ]groups:
18
+ |.+[ ]rejected[ ]your[ ]message[ ]to[ ]the[ ]following[ ]e[-]?mail[ ]addresses:
19
+ )
20
+ }x,
21
+ :error => %r/\ADiagnostic information for administrators:\z/,
22
+ :eoerr => %r/\AOriginal message headers:\z/,
23
+ :rfc822 => %r|\AContent-Type: message/rfc822\z|,
24
+ :endof => %r/\A__END_OF_EMAIL_MESSAGE__\z/,
25
+ }
26
+ CodeTable = {
27
+ '4.4.7' => 'expired',
28
+ '5.1.0' => 'rejected',
29
+ '5.1.1' => 'userunknown',
30
+ '5.1.10' => 'filtered',
31
+ '5.4.1' => 'networkerror',
32
+ '5.4.14' => 'networkerror',
33
+ '5.7.1' => 'rejected',
34
+ '5.7.133' => 'rejected',
35
+ }
36
+ Indicators = Sisimai::MSP.INDICATORS
37
+ LongFields = Sisimai::RFC5322.LONGFIELDS
38
+ RFC822Head = Sisimai::RFC5322.HEADERFIELDS
39
+
40
+ def description; return 'Microsoft Office 365: http://office.microsoft.com/'; end
41
+ def smtpagent; return 'US::Office365'; end
42
+
43
+ def headerlist
44
+ # X-MS-Exchange-Message-Is-Ndr:
45
+ # X-Microsoft-Antispam-PRVS: <....@...outlook.com>
46
+ # X-Exchange-Antispam-Report-Test: UriScan:;
47
+ # X-Exchange-Antispam-Report-CFA-Test:
48
+ # X-MS-Exchange-CrossTenant-OriginalArrivalTime: 29 Apr 2015 23:34:45.6789 (JST)
49
+ # X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted
50
+ # X-MS-Exchange-Transport-CrossTenantHeadersStamped: ...
51
+ return [
52
+ 'X-MS-Exchange-Message-Is-Ndr',
53
+ 'X-Microsoft-Antispam-PRVS',
54
+ 'X-Exchange-Antispam-Report-Test',
55
+ 'X-Exchange-Antispam-Report-CFA-Test',
56
+ 'X-MS-Exchange-CrossTenant-OriginalArrivalTime',
57
+ 'X-MS-Exchange-CrossTenant-FromEntityHeader',
58
+ 'X-MS-Exchange-Transport-CrossTenantHeadersStamped',
59
+ ]
60
+ end
61
+ def pattern; return Re0; end
62
+
63
+ # Parse bounce messages from Microsoft Office 365
64
+ # @param [Hash] mhead Message header of a bounce email
65
+ # @options mhead [String] from From header
66
+ # @options mhead [String] date Date header
67
+ # @options mhead [String] subject Subject header
68
+ # @options mhead [Array] received Received headers
69
+ # @options mhead [String] others Other required headers
70
+ # @param [String] mbody Message body of a bounce email
71
+ # @return [Hash, Nil] Bounce data list and message/rfc822
72
+ # part or nil if it failed to parse or
73
+ # the arguments are missing
74
+ def scan(mhead, mbody)
75
+ return nil unless mhead
76
+ return nil unless mbody
77
+
78
+ match = 0
79
+ match += 1 if mhead['subject'] =~ Re0[:subject]
80
+ match += 1 if mhead['x-ms-exchange-message-is-ndr']
81
+ match += 1 if mhead['x-microsoft-antispam-prvs']
82
+ match += 1 if mhead['x-exchange-antispam-report-test']
83
+ match += 1 if mhead['x-exchange-antispam-report-cfa-test']
84
+ match += 1 if mhead['x-ms-exchange-crosstenant-originalarrivaltime']
85
+ match += 1 if mhead['x-ms-exchange-crosstenant-fromentityheader']
86
+ match += 1 if mhead['x-ms-exchange-transport-crosstenantheadersstamped']
87
+ match += 1 if mhead['received'].find { |a| a =~ Re0[:received] }
88
+ return nil if match < 2
89
+
90
+ if mbody =~ /^Content-Transfer-Encoding: quoted-printable$/
91
+ # This is a multi-part message in MIME format. Your mail reader does not
92
+ # understand MIME message format.
93
+ # --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ
94
+ # Content-Type: text/plain; charset=iso-8859-15
95
+ # Content-Transfer-Encoding: quoted-printable
96
+ require 'sisimai/mime'
97
+ mbody = mbody.sub(/\A.+?quoted-printable/ms, '')
98
+ mbody = Sisimai::MIME.qprintd(mbody)
99
+ end
100
+
101
+ dscontents = []; dscontents << Sisimai::MSP.DELIVERYSTATUS
102
+ hasdivided = mbody.split("\n")
103
+ rfc822next = { 'from' => false, 'to' => false, 'subject' => false }
104
+ rfc822part = '' # (String) message/rfc822-headers part
105
+ previousfn = '' # (String) Previous field name
106
+ readcursor = 0 # (Integer) Points the current cursor position
107
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
108
+ connheader = {}
109
+ endoferror = false # (Boolean) Flag for the end of error messages
110
+ htmlbegins = false # (Boolean) Flag for HTML part
111
+ v = nil
112
+
113
+ hasdivided.each do |e|
114
+ if readcursor == 0
115
+ # Beginning of the bounce message or delivery status part
116
+ if e =~ Re1[:begin]
117
+ readcursor |= Indicators[:'deliverystatus']
118
+ next
119
+ end
120
+ end
121
+
122
+ if readcursor & Indicators[:'message-rfc822'] == 0
123
+ # Beginning of the original message part
124
+ if e =~ Re1[:rfc822]
125
+ readcursor |= Indicators[:'message-rfc822']
126
+ next
127
+ end
128
+ end
129
+
130
+ if readcursor & Indicators[:'message-rfc822'] > 0
131
+ # After "message/rfc822"
132
+ if cv = e.match(/\A([-0-9A-Za-z]+?)[:][ ]*.+\z/)
133
+ # Get required headers only
134
+ lhs = cv[1].downcase
135
+ previousfn = ''
136
+ next unless RFC822Head.key?(lhs)
137
+
138
+ previousfn = lhs
139
+ rfc822part += e + "\n"
140
+
141
+ elsif e =~ /\A[ \t]+/
142
+ # Continued line from the previous line
143
+ next if rfc822next[previousfn]
144
+ rfc822part += e + "\n" if LongFields.key?(previousfn)
145
+
146
+ else
147
+ # Check the end of headers in rfc822 part
148
+ next unless LongFields.key?(previousfn)
149
+ next unless e.empty?
150
+ rfc822next[previousfn] = true
151
+ end
152
+
153
+ else
154
+ # Before "message/rfc822"
155
+ next if readcursor & Indicators[:'deliverystatus'] == 0
156
+ next if e.empty?
157
+
158
+ # kijitora@example.com<mailto:kijitora@example.com>
159
+ # The email address wasn=92t found at the destination domain. It might be mis=
160
+ # spelled or it might not exist any longer. Try retyping the address and rese=
161
+ # nding the message.
162
+ v = dscontents[-1]
163
+
164
+ if cv = e.match(/\A.+[@].+[<]mailto:(.+[@].+)[>]\z/)
165
+ # kijitora@example.com<mailto:kijitora@example.com>
166
+ if v['recipient']
167
+ # There are multiple recipient addresses in the message body.
168
+ dscontents << Sisimai::MSP.DELIVERYSTATUS
169
+ v = dscontents[-1]
170
+ end
171
+ v['recipient'] = cv[1]
172
+ recipients += 1
173
+
174
+ elsif cv = e.match(/\AGenerating server: (.+)\z/)
175
+ # Generating server: FFFFFFFFFFFF.e0.prod.outlook.com
176
+ connheader['lhost'] = cv[1].downcase
177
+
178
+ else
179
+ if endoferror
180
+ # After "Original message headers:"
181
+ if htmlbegins
182
+ # <html> .. </html>
183
+ htmlbegins = false if e =~ %r|\A[<]/html[>]|
184
+ next
185
+ end
186
+
187
+ if cv = e.match(/\A[Aa]ction:[ ]*(.+)\z/)
188
+ # Action: failed
189
+ v['action'] = cv[1].downcase
190
+
191
+ elsif cv = e.match(/\A[Ss]tatus:[ ]*(\d[.]\d+[.]\d+)/)
192
+ # Status:5.2.0
193
+ v['status'] = cv[1]
194
+
195
+ elsif cv = e.match(/\A[Rr]eporting-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
196
+ # Reporting-MTA: dns;BLU004-OMC3S13.hotmail.example.com
197
+ connheader['lhost'] = cv[1].downcase
198
+
199
+ elsif cv = e.match(/\A[Rr]eceived-[Ff]rom-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
200
+ # Reporting-MTA: dns;BLU004-OMC3S13.hotmail.example.com
201
+ connheader['rhost'] = cv[1].downcase
202
+
203
+ elsif cv = e.match(/\A[Aa]rrival-[Dd]ate:[ ]*(.+)\z/)
204
+ # Arrival-Date: Wed, 29 Apr 2009 16:03:18 +0900
205
+ next if connheader['date']
206
+ connheader['date'] = cv[1]
207
+
208
+ else
209
+ htmlbegins = true if e =~ /\A[<]html[>]/
210
+ end
211
+
212
+ else
213
+ if e =~ Re1[:error]
214
+ # Diagnostic information for administrators:
215
+ v['diagnosis'] = e
216
+ else
217
+ # kijitora@example.com
218
+ # Remote Server returned '550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipien=
219
+ # t not found by SMTP address lookup'
220
+ next unless v['diagnosis']
221
+ if e =~ Re1[:eoerr]
222
+ # Original message headers:
223
+ endoferror = true
224
+ next
225
+ end
226
+ v['diagnosis'] += ' ' + e
227
+ end
228
+ end
229
+ end
230
+
231
+ end
232
+ end
233
+
234
+ return nil if recipients == 0
235
+ require 'sisimai/string'
236
+ require 'sisimai/smtp/status'
237
+
238
+ dscontents.map do |e|
239
+ # Set default values if each value is empty.
240
+ connheader.each_key { |a| e[a] ||= connheader[a] || '' }
241
+
242
+ if mhead['received'].size > 0
243
+ # Get localhost and remote host name from Received header.
244
+ r0 = mhead['received']
245
+ %w|lhost rhost|.each { |a| e[a] ||= '' }
246
+ e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
247
+ e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
248
+ end
249
+
250
+ e['spec'] ||= 'SMTP'
251
+ e['status'] ||= ''
252
+ e['agent'] = Sisimai::MSP::US::Office365.smtpagent
253
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
254
+
255
+ if e['status'].empty? || e['status'] =~ /\A\d[.]0[.]0\z/
256
+ # There is no value of Status header or the value is 5.0.0, 4.0.0
257
+ pseudostatus = Sisimai::SMTP::Status.find(e['diagnosis'])
258
+ e['status'] = pseudostatus if pseudostatus.size > 0
259
+ end
260
+
261
+ if e['status']
262
+ e['reason'] = CodeTable[e['status']] || ''
263
+ end
264
+ end
265
+
266
+ return { 'ds' => dscontents, 'rfc822' => rfc822part }
267
+ end
268
+
269
+ end
270
+ end
271
+ end
272
+ end
273
+