sisimai 4.23.0-java → 4.24.0-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.
- checksums.yaml +4 -4
- data/ANALYTICAL-PRECISION +31 -21
- data/ChangeLog.md +20 -1
- data/README-JA.md +4 -4
- data/README.md +4 -4
- data/lib/sisimai/address.rb +1 -3
- data/lib/sisimai/bite/email/activehunter.rb +1 -0
- data/lib/sisimai/bite/email/amazonses.rb +2 -4
- data/lib/sisimai/bite/email/amazonworkmail.rb +5 -9
- data/lib/sisimai/bite/email/apachejames.rb +8 -4
- data/lib/sisimai/bite/email/exim.rb +3 -7
- data/lib/sisimai/bite/email/google.rb +1 -3
- data/lib/sisimai/bite/email/gsuite.rb +1 -0
- data/lib/sisimai/bite/email/interscanmss.rb +1 -1
- data/lib/sisimai/bite/email/mailru.rb +3 -7
- data/lib/sisimai/bite/email/mxlogic.rb +3 -7
- data/lib/sisimai/bite/email/qmail.rb +3 -6
- data/lib/sisimai/bite/email/x4.rb +3 -6
- data/lib/sisimai/bite/email/yahoo.rb +2 -4
- data/lib/sisimai/bite/json/amazonses.rb +2 -4
- data/lib/sisimai/data.rb +13 -23
- data/lib/sisimai/message/email.rb +24 -39
- data/lib/sisimai/mime.rb +214 -38
- data/lib/sisimai/reason.rb +1 -3
- data/lib/sisimai/reason/blocked.rb +2 -0
- data/lib/sisimai/rfc3464.rb +8 -12
- data/lib/sisimai/rfc5322.rb +1 -3
- data/lib/sisimai/string.rb +8 -6
- data/lib/sisimai/version.rb +1 -1
- data/set-of-emails/maildir/bsd/email-apachejames-01.eml +1 -2
- data/set-of-emails/maildir/bsd/{rfc3464-02.eml → email-domino-03.eml} +0 -0
- data/set-of-emails/maildir/bsd/email-office365-08.eml +49 -51
- data/set-of-emails/maildir/bsd/email-outlook-08.eml +3 -2
- data/set-of-emails/maildir/bsd/email-sendmail-56.eml +86 -0
- data/set-of-emails/maildir/bsd/email-verizon-02.eml +1 -2
- metadata +4 -4
- data/lib/sisimai/rfc2606.rb +0 -23
@@ -95,10 +95,8 @@ module Sisimai::Bite::Email
|
|
95
95
|
# Remote host said: 550 5.1.1 <kijitora@example.org>... User Unknown [RCPT_TO]
|
96
96
|
v['diagnosis'] = e
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
v['command'] = cv[1]
|
101
|
-
end
|
98
|
+
# Get SMTP command from the value of "Remote host said:"
|
99
|
+
if cv = e.match(/\[([A-Z]{4}).*\]\z/) then v['command'] = cv[1] end
|
102
100
|
else
|
103
101
|
# <mailboxfull@example.jp>:
|
104
102
|
# Remote host said:
|
@@ -149,10 +149,8 @@ module Sisimai::Bite::JSON
|
|
149
149
|
v['diagnosis'] = e['diagnosticCode']
|
150
150
|
end
|
151
151
|
|
152
|
-
|
153
|
-
|
154
|
-
v['lhost'] = cv[1]
|
155
|
-
end
|
152
|
+
# 'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
|
153
|
+
if cv = o['reportingMTA'].match(/\Adsn;[ ](.+)\z/) then v['lhost'] = cv[1] end
|
156
154
|
|
157
155
|
if BounceType.key?(o['bounceType'].to_sym) &&
|
158
156
|
BounceType[o['bounceType'].to_sym].key?(o['bounceSubType'].to_sym)
|
data/lib/sisimai/data.rb
CHANGED
@@ -94,7 +94,6 @@ module Sisimai
|
|
94
94
|
return nil unless data.is_a? Sisimai::Message
|
95
95
|
|
96
96
|
messageobj = data
|
97
|
-
mailheader = data.header
|
98
97
|
rfc822data = messageobj.rfc822
|
99
98
|
fieldorder = { :recipient => [], :addresser => [] }
|
100
99
|
objectlist = []
|
@@ -197,14 +196,12 @@ module Sisimai
|
|
197
196
|
break if datestring
|
198
197
|
end
|
199
198
|
|
200
|
-
if datestring
|
199
|
+
if datestring && cv = datestring.match(/\A(.+)[ ]+([-+]\d{4})\z/)
|
201
200
|
# Get the value of timezone offset from datestring
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
p['timezoneoffset'] = cv[2]
|
207
|
-
end
|
201
|
+
# Wed, 26 Feb 2014 06:05:48 -0500
|
202
|
+
datestring = cv[1]
|
203
|
+
zoneoffset = Sisimai::DateTime.tz2second(cv[2])
|
204
|
+
p['timezoneoffset'] = cv[2]
|
208
205
|
end
|
209
206
|
|
210
207
|
begin
|
@@ -218,7 +215,7 @@ module Sisimai
|
|
218
215
|
next unless p['timestamp']
|
219
216
|
|
220
217
|
# OTHER_TEXT_HEADERS:
|
221
|
-
recvheader =
|
218
|
+
recvheader = data.header['received'] || []
|
222
219
|
unless recvheader.empty?
|
223
220
|
# Get localhost and remote host name from Received header.
|
224
221
|
%w[lhost rhost].each { |a| e[a] ||= '' }
|
@@ -244,11 +241,8 @@ module Sisimai
|
|
244
241
|
# The value of "List-Id" header
|
245
242
|
p['listid'] = rfc822data['list-id'] || ''
|
246
243
|
unless p['listid'].empty?
|
247
|
-
# Get the value of List-Id header
|
248
|
-
if cv = p['listid'].match(/\A.*([<].+[>]).*\z/)
|
249
|
-
# List name <list-id@example.org>
|
250
|
-
p['listid'] = cv[1]
|
251
|
-
end
|
244
|
+
# Get the value of List-Id header like "List name <list-id@example.org>"
|
245
|
+
if cv = p['listid'].match(/\A.*([<].+[>]).*\z/) then p['listid'] = cv[1] end
|
252
246
|
p['listid'] = p['listid'].delete('<>').chomp("\r")
|
253
247
|
p['listid'] = '' if p['listid'].include?(' ')
|
254
248
|
end
|
@@ -256,11 +250,9 @@ module Sisimai
|
|
256
250
|
# The value of "Message-Id" header
|
257
251
|
p['messageid'] = rfc822data['message-id'] || ''
|
258
252
|
unless p['messageid'].empty?
|
259
|
-
#
|
260
|
-
if cv = p['messageid'].match(/\A([^ ]+)[ ].*/)
|
261
|
-
|
262
|
-
end
|
263
|
-
p['messageid'] = p['messageid'].delete('<>').chomp("\r")
|
253
|
+
# Leave only string inside of angle brackets(<>)
|
254
|
+
if cv = p['messageid'].match(/\A([^ ]+)[ ].*/) then p['messageid'] = cv[1] end
|
255
|
+
if cv = p['messageid'].match(/[<]([^ ]+?)[>]/) then p['messageid'] = cv[1] end
|
264
256
|
end
|
265
257
|
|
266
258
|
# CHECK_DELIVERY_STATUS_VALUE:
|
@@ -307,10 +299,8 @@ module Sisimai
|
|
307
299
|
|
308
300
|
# Check the value of "action"
|
309
301
|
if p['action'].size > 0
|
310
|
-
|
311
|
-
|
312
|
-
p['action'] = cv[1]
|
313
|
-
end
|
302
|
+
# Action: expanded (to multi-recipient alias)
|
303
|
+
if cv = p['action'].match(/\A(.+?) .+/) then p['action'] = cv[1] end
|
314
304
|
|
315
305
|
unless %w[failed delayed delivered relayed expanded].index(p['action'])
|
316
306
|
# The value of "action" is not in the following values:
|
@@ -29,7 +29,6 @@ module Sisimai
|
|
29
29
|
DefaultSet = Sisimai::Order::Email.another
|
30
30
|
SubjectTab = Sisimai::Order::Email.by('subject')
|
31
31
|
ExtHeaders = Sisimai::Order::Email.headers
|
32
|
-
ReEncoding = Sisimai::MIME.patterns
|
33
32
|
|
34
33
|
# Make data structure from the email message(a body part and headers)
|
35
34
|
# @param [Hash] argvs Email data
|
@@ -392,22 +391,34 @@ module Sisimai
|
|
392
391
|
mailheader['subject'] ||= ''
|
393
392
|
mailheader['content-type'] ||= ''
|
394
393
|
|
394
|
+
if hookmethod.is_a? Proc
|
395
|
+
# Call the hook method
|
396
|
+
begin
|
397
|
+
p = {
|
398
|
+
'datasrc' => 'email',
|
399
|
+
'headers' => mailheader,
|
400
|
+
'message' => bodystring,
|
401
|
+
'bounces' => nil
|
402
|
+
}
|
403
|
+
havecaught = hookmethod.call(p)
|
404
|
+
rescue StandardError => ce
|
405
|
+
warn ' ***warning: Something is wrong in hook method :' << ce.to_s
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
395
409
|
# Decode BASE64 Encoded message body, rewrite.
|
396
410
|
mesgformat = (mailheader['content-type'] || '').downcase
|
397
411
|
ctencoding = (mailheader['content-transfer-encoding'] || '').downcase
|
398
412
|
|
399
413
|
if mesgformat.start_with?('text/plain', 'text/html')
|
400
414
|
# Content-Type: text/plain; charset=UTF-8
|
401
|
-
if ctencoding == 'base64'
|
415
|
+
if ctencoding == 'base64'
|
402
416
|
# Content-Transfer-Encoding: base64
|
417
|
+
bodystring = Sisimai::MIME.base64d(bodystring)
|
418
|
+
|
419
|
+
elsif ctencoding == 'quoted-printable'
|
403
420
|
# Content-Transfer-Encoding: quoted-printable
|
404
|
-
bodystring =
|
405
|
-
# Content-Transfer-Encoding: base64
|
406
|
-
Sisimai::MIME.base64d(bodystring)
|
407
|
-
else
|
408
|
-
# Content-Transfer-Encoding: quoted-printable
|
409
|
-
Sisimai::MIME.qprintd(bodystring)
|
410
|
-
end
|
421
|
+
bodystring = Sisimai::MIME.qprintd(bodystring)
|
411
422
|
end
|
412
423
|
|
413
424
|
if mesgformat.start_with?('text/html;')
|
@@ -416,35 +427,10 @@ module Sisimai
|
|
416
427
|
end
|
417
428
|
else
|
418
429
|
# NOT text/plain
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
bodystring =
|
423
|
-
end
|
424
|
-
|
425
|
-
if lowercased =~ ReEncoding[:'7bit-encoded'] &&
|
426
|
-
cv = lowercased.match(ReEncoding[:'some-iso2022'])
|
427
|
-
# Content-Transfer-Encoding: 7bit
|
428
|
-
# Content-Type: text/plain; charset=ISO-2022-JP
|
429
|
-
if ! cv[1].include?('us-ascii') && ! cv[1].include?('utf-8')
|
430
|
-
bodystring = Sisimai::String.to_utf8(bodystring, cv[1])
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
# Call the hook method
|
436
|
-
if hookmethod.is_a? Proc
|
437
|
-
# Execute hook method
|
438
|
-
begin
|
439
|
-
p = {
|
440
|
-
'datasrc' => 'email',
|
441
|
-
'headers' => mailheader,
|
442
|
-
'message' => bodystring,
|
443
|
-
'bounces' => nil
|
444
|
-
}
|
445
|
-
havecaught = hookmethod.call(p)
|
446
|
-
rescue StandardError => ce
|
447
|
-
warn ' ***warning: Something is wrong in hook method :' << ce.to_s
|
430
|
+
if mesgformat.start_with?('multipart/')
|
431
|
+
# In case of Content-Type: multipart/*
|
432
|
+
p = Sisimai::MIME.makeflat(mailheader['content-type'], bodystring)
|
433
|
+
bodystring = p unless p.empty?
|
448
434
|
end
|
449
435
|
end
|
450
436
|
|
@@ -469,7 +455,6 @@ module Sisimai
|
|
469
455
|
# 4. Sisimai::Bite::Email::*
|
470
456
|
# 5. Sisimai::RFC3464
|
471
457
|
# 6. Sisimai::RFC3834
|
472
|
-
#
|
473
458
|
if Sisimai::ARF.is_arf(mailheader)
|
474
459
|
# Feedback Loop message
|
475
460
|
scannedset = Sisimai::ARF.scan(mailheader, bodystring)
|
data/lib/sisimai/mime.rb
CHANGED
@@ -7,11 +7,12 @@ module Sisimai
|
|
7
7
|
require 'sisimai/string'
|
8
8
|
|
9
9
|
ReE = {
|
10
|
-
:'7bit-encoded' => %r/^content-transfer-encoding:[ ]*7bit
|
11
|
-
:'quoted-print' => %r/^content-transfer-encoding:[ ]*quoted-printable
|
12
|
-
:'some-iso2022' => %r/^content-type:[ ]*.+;[ ]*charset=["']?(iso-2022-[-a-z0-9]+?)['"]
|
13
|
-
:'
|
14
|
-
:'
|
10
|
+
:'7bit-encoded' => %r/^content-transfer-encoding:[ ]*7bit/m,
|
11
|
+
:'quoted-print' => %r/^content-transfer-encoding:[ ]*quoted-printable/m,
|
12
|
+
:'some-iso2022' => %r/^content-type:[ ]*.+;[ ]*charset=["']?(iso-2022-[-a-z0-9]+?)['"]?\b/m,
|
13
|
+
:'another-8bit' => %r/^content-type:[ ]*.+;[ ]*charset=["']?(.+?)['"]?\b/m,
|
14
|
+
:'with-charset' => %r/^content[-]type:[ ]*.+[;][ ]*charset=['"]?(.+?)['"]?\b/,
|
15
|
+
:'only-charset' => %r/^[\s\t]+charset=['"]?(.+?)['"]?\b/,
|
15
16
|
:'html-message' => %r|^content-type:[ ]*text/html;|m,
|
16
17
|
}.freeze
|
17
18
|
|
@@ -53,36 +54,30 @@ module Sisimai
|
|
53
54
|
def mimedecode(argvs = [])
|
54
55
|
characterset = nil
|
55
56
|
encodingname = nil
|
56
|
-
mimeencoded0 = nil
|
57
57
|
decodedtext0 = []
|
58
|
-
notmimetext0 = ''
|
59
|
-
notmimetext1 = ''
|
60
58
|
|
61
59
|
while e = argvs.shift do
|
62
60
|
# Check and decode each element
|
63
61
|
e = e.strip.delete('"')
|
64
62
|
|
65
63
|
if self.is_mimeencoded(e)
|
66
|
-
# MIME Encoded string
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Base64
|
82
|
-
decodedtext0 << Base64.decode64(mimeencoded0)
|
83
|
-
end
|
84
|
-
decodedtext0 << notmimetext1
|
64
|
+
# MIME Encoded string like "=?utf-8?B?55m954yr44Gr44KD44KT44GT?="
|
65
|
+
next unless cv = e.match(/\A(.*)=[?]([-_0-9A-Za-z]+)[?]([BbQq])[?](.+)[?]=?(.*)\z/)
|
66
|
+
|
67
|
+
characterset ||= cv[2]
|
68
|
+
encodingname ||= cv[3]
|
69
|
+
mimeencoded0 = cv[4]
|
70
|
+
decodedtext0 << cv[1]
|
71
|
+
|
72
|
+
if encodingname == 'Q'
|
73
|
+
# Quoted-Printable
|
74
|
+
decodedtext0 << mimeencoded0.unpack('M').first
|
75
|
+
|
76
|
+
elsif encodingname == 'B'
|
77
|
+
# Base64
|
78
|
+
decodedtext0 << Base64.decode64(mimeencoded0)
|
85
79
|
end
|
80
|
+
decodedtext0 << cv[5]
|
86
81
|
else
|
87
82
|
decodedtext0 << e
|
88
83
|
end
|
@@ -129,13 +124,10 @@ module Sisimai
|
|
129
124
|
boundary01 = Sisimai::MIME.boundary(heads['content-type'], 1)
|
130
125
|
bodystring = ''
|
131
126
|
notdecoded = ''
|
132
|
-
getencoded = ''
|
133
|
-
lowercased = ''
|
134
127
|
|
135
128
|
encodename = nil
|
136
129
|
ctencoding = nil
|
137
130
|
mimeinside = false
|
138
|
-
mustencode = false
|
139
131
|
hasdivided = argv1.split("\n")
|
140
132
|
|
141
133
|
while e = hasdivided.shift do
|
@@ -149,8 +141,8 @@ module Sisimai
|
|
149
141
|
if e == boundary00
|
150
142
|
# The next boundary string has appeared
|
151
143
|
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ
|
152
|
-
|
153
|
-
bodystring <<
|
144
|
+
hasdecoded = Sisimai::String.to_utf8(notdecoded.unpack('M').first, encodename)
|
145
|
+
bodystring << hasdecoded << e + "\n"
|
154
146
|
|
155
147
|
notdecoded = ''
|
156
148
|
mimeinside = false
|
@@ -234,10 +226,7 @@ module Sisimai
|
|
234
226
|
return nil unless argv1
|
235
227
|
|
236
228
|
plain = nil
|
237
|
-
if cv = argv1.match(%r|([+/\=0-9A-Za-z\r\n]+)|)
|
238
|
-
# Decode BASE64
|
239
|
-
plain = Base64.decode64(cv[1])
|
240
|
-
end
|
229
|
+
if cv = argv1.match(%r|([+/\=0-9A-Za-z\r\n]+)|) then plain = Base64.decode64(cv[1]) end
|
241
230
|
return plain.force_encoding('UTF-8')
|
242
231
|
end
|
243
232
|
|
@@ -256,15 +245,202 @@ module Sisimai
|
|
256
245
|
# Content-Type: multipart/report; report-type=delivery-status;
|
257
246
|
# boundary="n6H9lKZh014511.1247824040/mx.example.jp"
|
258
247
|
value = cv[1]
|
259
|
-
value.delete!(%q|'"
|
248
|
+
value.delete!(%q|'";\\|)
|
260
249
|
value = '--' + value if start > -1
|
261
250
|
value = value + '--' if start > 0
|
262
251
|
end
|
263
252
|
|
264
253
|
return value
|
265
254
|
end
|
266
|
-
end
|
267
255
|
|
256
|
+
# Breaks up each multipart/* block
|
257
|
+
# @param [String] argv0 Text block of multipart/*
|
258
|
+
# @param [String] argv1 MIME type of the outside part
|
259
|
+
# @return [String] Decoded part as a plain text(text part only)
|
260
|
+
def breaksup(argv0 = nil, argv1 = '')
|
261
|
+
return nil unless argv0
|
262
|
+
|
263
|
+
hasflatten = '' # Message body including only text/plain and message/*
|
264
|
+
alsoappend = %r{\A(?:text/rfc822-headers|message/)}
|
265
|
+
thisformat = %r/\A(?:Content-Transfer-Encoding:\s*.+\n)?Content-Type:\s*([^ ;]+)/
|
266
|
+
leavesonly = %r{\A(?>
|
267
|
+
text/(?:plain|html|rfc822-headers)
|
268
|
+
|message/(?:x?delivery-status|rfc822|partial|feedback-report)
|
269
|
+
|multipart/(?:report|alternative|mixed|related|partial)
|
270
|
+
)
|
271
|
+
}x
|
272
|
+
|
273
|
+
mimeformat = '' # MIME type string of this part
|
274
|
+
alternates = argv1.start_with?('multipart/alternative') ? true : false
|
275
|
+
|
276
|
+
# Get MIME type string from Content-Type: "..." field at the first line
|
277
|
+
# or the second line of the part.
|
278
|
+
if cv = argv0.match(thisformat) then mimeformat = cv[1].downcase end
|
279
|
+
|
280
|
+
# Sisimai require only MIME types defined in $leavesonly variable
|
281
|
+
return '' unless mimeformat =~ leavesonly
|
282
|
+
return '' if alternates && mimeformat == 'text/html'
|
283
|
+
|
284
|
+
(upperchunk, lowerchunk) = argv0.split(/^$/m, 2)
|
285
|
+
upperchunk.gsub!("\n", ' ').squeeze(' ')
|
286
|
+
|
287
|
+
# Content-Description: Undelivered Message
|
288
|
+
# Content-Type: message/rfc822
|
289
|
+
# <EOM>
|
290
|
+
lowerchunk ||= ''
|
291
|
+
|
292
|
+
if mimeformat.start_with?('multipart/')
|
293
|
+
# Content-Type: multipart/*
|
294
|
+
mpboundary = Regexp.new(Regexp.escape(Sisimai::MIME.boundary(upperchunk, 0)) << "\n")
|
295
|
+
innerparts = lowerchunk.split(mpboundary)
|
296
|
+
|
297
|
+
innerparts.shift if innerparts[0].empty?
|
298
|
+
while e = innerparts.shift do
|
299
|
+
# Find internal multipart/* blocks and decode
|
300
|
+
if cv = e.match(thisformat)
|
301
|
+
# Found "Content-Type" field at the first or second line of this
|
302
|
+
# splitted part
|
303
|
+
nextformat = cv[1].downcase
|
304
|
+
|
305
|
+
next unless nextformat =~ leavesonly
|
306
|
+
next if nextformat == 'text/html'
|
307
|
+
|
308
|
+
hasflatten << Sisimai::MIME.breaksup(e, mimeformat)
|
309
|
+
else
|
310
|
+
# The content of this part is almost '--': a part of boundary
|
311
|
+
# string which is used for splitting multipart/* blocks.
|
312
|
+
hasflatten << "\n"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
else
|
316
|
+
# Is not "Content-Type: multipart/*"
|
317
|
+
if cv = upperchunk.match(/Content-Transfer-Encoding: ([^\s;]+)/)
|
318
|
+
# Content-Transfer-Encoding: quoted-printable|base64|7bit|...
|
319
|
+
ctencoding = cv[1].downcase
|
320
|
+
getdecoded = ''
|
321
|
+
|
322
|
+
if ctencoding == 'quoted-printable'
|
323
|
+
# Content-Transfer-Encoding: quoted-printable
|
324
|
+
getdecoded = Sisimai::MIME.qprintd(lowerchunk)
|
325
|
+
|
326
|
+
elsif ctencoding == 'base64'
|
327
|
+
# Content-Transfer-Encoding: base64
|
328
|
+
getdecoded = Sisimai::MIME.base64d(lowerchunk)
|
329
|
+
|
330
|
+
elsif ctencoding == '7bit'
|
331
|
+
# Content-Transfer-Encoding: 7bit
|
332
|
+
if cv = upperchunk.downcase.match(ReE[:'some-iso2022'])
|
333
|
+
# Content-Type: text/plain; charset=ISO-2022-JP
|
334
|
+
getdecoded = Sisimai::String.to_utf8(lowerchunk, cv[1])
|
335
|
+
else
|
336
|
+
# No "charset" parameter in Content-Type field
|
337
|
+
getdecoded = lowerchunk
|
338
|
+
end
|
339
|
+
else
|
340
|
+
# Content-Transfer-Encoding: 8bit, binary, and so on
|
341
|
+
getdecoded = lowerchunk
|
342
|
+
end
|
343
|
+
getdecoded.gsub!(/\r\n/, "\n") # Convert CRLF to LF
|
344
|
+
|
345
|
+
if mimeformat =~ alsoappend
|
346
|
+
# Append field when the value of Content-Type: begins with
|
347
|
+
# message/ or equals text/rfc822-headers.
|
348
|
+
upperchunk.sub!(/Content-Transfer-Encoding:.+\z/, '').gsub!(/[ ]\z/, '')
|
349
|
+
hasflatten << upperchunk
|
350
|
+
|
351
|
+
elsif mimeformat == 'text/html'
|
352
|
+
# Delete HTML tags inside of text/html part whenever possible
|
353
|
+
getdecoded.gsub!(/[<][^@ ]+?[>]/, '')
|
354
|
+
end
|
355
|
+
|
356
|
+
unless getdecoded.empty?
|
357
|
+
# The string will be encoded to UTF-8 forcely and call String#scrub
|
358
|
+
# method to avoid the following errors:
|
359
|
+
# - incompatible character encodings: ASCII-8BIT and UTF-8
|
360
|
+
# - invalid byte sequence in UTF-8
|
361
|
+
unless getdecoded.encoding.to_s == 'UTF-8'
|
362
|
+
if cv = upperchunk.downcase.match(ReE[:'another-8bit'])
|
363
|
+
# ISO-8859-1, GB2312, and so on
|
364
|
+
getdecoded = Sisimai::String.to_utf8(getdecoded, cv[1])
|
365
|
+
end
|
366
|
+
end
|
367
|
+
# A part which has no "charset" parameter causes an ArgumentError:
|
368
|
+
# invalid byte sequence in UTF-8 so String#scrub should be called
|
369
|
+
hasflatten << getdecoded.scrub!('?') << "\n\n"
|
370
|
+
end
|
371
|
+
else
|
372
|
+
# Content-Type: text/plain OR text/rfc822-headers OR message/*
|
373
|
+
if mimeformat.start_with?('message/') || mimeformat == 'text/rfc822-headers'
|
374
|
+
# Append headers of multipart/* when the value of "Content-Type"
|
375
|
+
# is inlucded in the following MIME types:
|
376
|
+
# - message/delivery-status
|
377
|
+
# - message/rfc822
|
378
|
+
# - text/rfc822-headers
|
379
|
+
hasflatten << upperchunk
|
380
|
+
end
|
381
|
+
lowerchunk.sub!(/^--\z/m, '')
|
382
|
+
lowerchunk << "\n" unless lowerchunk =~ /\n\z/
|
383
|
+
hasflatten << lowerchunk
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
return hasflatten
|
388
|
+
end
|
389
|
+
|
390
|
+
# MIME decode entire message body
|
391
|
+
# @param [String] argv0 Content-Type header
|
392
|
+
# @param [String] argv1 Entire message body
|
393
|
+
# @return [String] Decoded message body
|
394
|
+
def makeflat(argv0 = nil, argv1 = nil)
|
395
|
+
return nil unless argv0
|
396
|
+
return nil unless argv1
|
397
|
+
|
398
|
+
ehboundary = Sisimai::MIME.boundary(argv0, 0)
|
399
|
+
mimeformat = ''
|
400
|
+
bodystring = ''
|
401
|
+
|
402
|
+
# Get MIME type string from an email header given as the 1st argument
|
403
|
+
if cv = argv0.match(%r|\A([0-9a-z]+/[^ ;]+)|) then mimeformat = cv[1] end
|
404
|
+
|
405
|
+
return '' unless mimeformat.include?('multipart/')
|
406
|
+
return '' if ehboundary.empty?
|
407
|
+
|
408
|
+
# Some bounce messages include lower-cased "content-type:" field such as
|
409
|
+
# content-type: message/delivery-status
|
410
|
+
# content-transfer-encoding: quoted-printable
|
411
|
+
argv1.gsub!(/[Cc]ontent-[Tt]ype:/m, 'Content-Type:')
|
412
|
+
argv1.gsub!(/[Cc]ontent-[Tt]ransfer-[Ee]ncodeing:/m, 'Content-Transfer-Encoding:')
|
413
|
+
|
414
|
+
# 1. Some bounce messages include upper-cased "Content-Transfer-Encoding",
|
415
|
+
# and "Content-Type" value such as
|
416
|
+
# - Content-Type: multipart/RELATED;
|
417
|
+
# - Content-Transfer-Encoding: 7BIT
|
418
|
+
# 2. Unused fields inside of mutipart/* block should be removed
|
419
|
+
argv1.gsub!(/(Content-[A-Za-z-]+?):[ ]*([^\s]+)/) do "#{$1}: #{$2.downcase}" end
|
420
|
+
argv1.gsub!(/^Content-(?:Description|Disposition):.+$/, '')
|
421
|
+
|
422
|
+
multiparts = argv1.split(Regexp.new(Regexp.escape(ehboundary) << "\n"))
|
423
|
+
multiparts.shift if multiparts[0].empty?
|
424
|
+
|
425
|
+
while e = multiparts.shift do
|
426
|
+
# Find internal multipart blocks and decode
|
427
|
+
if e =~ /\A(?:Content-[A-Za-z-]+:.+?\r\n)?Content-Type:[ ]*[^\s]+/
|
428
|
+
# Content-Type: multipart/*
|
429
|
+
bodystring << Sisimai::MIME.breaksup(e, mimeformat)
|
430
|
+
else
|
431
|
+
# Is not multipart/* block
|
432
|
+
e.sub!(%r|^Content-Transfer-Encoding:.+?\n|mi, '')
|
433
|
+
e.sub!(%r|^Content-Type:\s*text/plain.+?\n|mi, '')
|
434
|
+
bodystring << e
|
435
|
+
end
|
436
|
+
end
|
437
|
+
bodystring.gsub!(%r{^(Content-Type:\s*message/(?:rfc822|delivery-status)).+$}, '\1')
|
438
|
+
bodystring.gsub!(/^\n{2,}/, "\n")
|
439
|
+
|
440
|
+
return bodystring
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
268
444
|
end
|
269
445
|
end
|
270
446
|
|