sisimai 4.25.16-java → 5.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +412 -393
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +15 -15
- data/README-JA.md +140 -78
- data/README.md +290 -143
- data/Rakefile +9 -3
- data/Repository.mk +2 -3
- data/lib/sisimai/address.rb +118 -74
- data/lib/sisimai/arf.rb +84 -82
- data/lib/sisimai/datetime.rb +5 -52
- data/lib/sisimai/{data → fact}/json.rb +7 -9
- data/lib/sisimai/fact/yaml.rb +31 -0
- data/lib/sisimai/fact.rb +468 -0
- data/lib/sisimai/lhost/activehunter.rb +12 -14
- data/lib/sisimai/lhost/amavis.rb +11 -14
- data/lib/sisimai/lhost/amazonses.rb +37 -41
- data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
- data/lib/sisimai/lhost/aol.rb +12 -14
- data/lib/sisimai/lhost/apachejames.rb +19 -21
- data/lib/sisimai/lhost/barracuda.rb +10 -12
- data/lib/sisimai/lhost/bigfoot.rb +21 -21
- data/lib/sisimai/lhost/biglobe.rb +15 -16
- data/lib/sisimai/lhost/courier.rb +20 -20
- data/lib/sisimai/lhost/domino.rb +23 -19
- data/lib/sisimai/lhost/einsundeins.rb +23 -18
- data/lib/sisimai/lhost/exchange2003.rb +30 -29
- data/lib/sisimai/lhost/exchange2007.rb +70 -58
- data/lib/sisimai/lhost/exim.rb +175 -161
- data/lib/sisimai/lhost/ezweb.rb +31 -56
- data/lib/sisimai/lhost/facebook.rb +21 -33
- data/lib/sisimai/lhost/fml.rb +43 -48
- data/lib/sisimai/lhost/gmail.rb +29 -29
- data/lib/sisimai/lhost/gmx.rb +18 -17
- data/lib/sisimai/lhost/googlegroups.rb +9 -10
- data/lib/sisimai/lhost/gsuite.rb +21 -27
- data/lib/sisimai/lhost/imailserver.rb +25 -39
- data/lib/sisimai/lhost/interscanmss.rb +28 -31
- data/lib/sisimai/lhost/kddi.rb +22 -28
- data/lib/sisimai/lhost/mailfoundry.rb +11 -12
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
- data/lib/sisimai/lhost/mailru.rb +33 -27
- data/lib/sisimai/lhost/mcafee.rb +21 -31
- data/lib/sisimai/lhost/messagelabs.rb +17 -20
- data/lib/sisimai/lhost/messagingserver.rb +40 -37
- data/lib/sisimai/lhost/mfilter.rb +15 -16
- data/lib/sisimai/lhost/mxlogic.rb +24 -23
- data/lib/sisimai/lhost/notes.rb +17 -17
- data/lib/sisimai/lhost/office365.rb +63 -27
- data/lib/sisimai/lhost/opensmtpd.rb +12 -13
- data/lib/sisimai/lhost/outlook.rb +12 -15
- data/lib/sisimai/lhost/postfix.rb +179 -129
- data/lib/sisimai/lhost/powermta.rb +12 -14
- data/lib/sisimai/lhost/qmail.rb +44 -47
- data/lib/sisimai/lhost/receivingses.rb +15 -20
- data/lib/sisimai/lhost/sendgrid.rb +34 -32
- data/lib/sisimai/lhost/sendmail.rb +66 -53
- data/lib/sisimai/lhost/surfcontrol.rb +19 -19
- data/lib/sisimai/lhost/v5sendmail.rb +45 -39
- data/lib/sisimai/lhost/verizon.rb +35 -39
- data/lib/sisimai/lhost/x1.rb +18 -17
- data/lib/sisimai/lhost/x2.rb +17 -14
- data/lib/sisimai/lhost/x3.rb +19 -19
- data/lib/sisimai/lhost/x4.rb +72 -57
- data/lib/sisimai/lhost/x5.rb +17 -19
- data/lib/sisimai/lhost/x6.rb +41 -17
- data/lib/sisimai/lhost/yahoo.rb +17 -16
- data/lib/sisimai/lhost/yandex.rb +16 -20
- data/lib/sisimai/lhost/zoho.rb +16 -15
- data/lib/sisimai/lhost.rb +8 -10
- data/lib/sisimai/mail/maildir.rb +1 -3
- data/lib/sisimai/mail/mbox.rb +3 -4
- data/lib/sisimai/mail/memory.rb +0 -1
- data/lib/sisimai/mail/stdin.rb +1 -3
- data/lib/sisimai/mail.rb +3 -7
- data/lib/sisimai/mda.rb +28 -42
- data/lib/sisimai/message.rb +435 -326
- data/lib/sisimai/order.rb +5 -5
- data/lib/sisimai/reason/authfailure.rb +64 -0
- data/lib/sisimai/reason/badreputation.rb +53 -0
- data/lib/sisimai/reason/blocked.rb +94 -160
- data/lib/sisimai/reason/contenterror.rb +8 -9
- data/lib/sisimai/reason/delivered.rb +4 -6
- data/lib/sisimai/reason/exceedlimit.rb +10 -12
- data/lib/sisimai/reason/expired.rb +6 -8
- data/lib/sisimai/reason/feedback.rb +2 -3
- data/lib/sisimai/reason/filtered.rb +17 -19
- data/lib/sisimai/reason/hasmoved.rb +9 -10
- data/lib/sisimai/reason/hostunknown.rb +15 -15
- data/lib/sisimai/reason/mailboxfull.rb +10 -12
- data/lib/sisimai/reason/mailererror.rb +18 -20
- data/lib/sisimai/reason/mesgtoobig.rb +9 -11
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +8 -11
- data/lib/sisimai/reason/notaccept.rb +13 -14
- data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +14 -12
- data/lib/sisimai/reason/rejected.rb +26 -24
- data/lib/sisimai/reason/requireptr.rb +69 -0
- data/lib/sisimai/reason/securityerror.rb +33 -36
- data/lib/sisimai/reason/spamdetected.rb +114 -147
- data/lib/sisimai/reason/speeding.rb +49 -0
- data/lib/sisimai/reason/suspend.rb +11 -11
- data/lib/sisimai/reason/syntaxerror.rb +11 -10
- data/lib/sisimai/reason/systemerror.rb +7 -9
- data/lib/sisimai/reason/systemfull.rb +7 -8
- data/lib/sisimai/reason/toomanyconn.rb +9 -11
- data/lib/sisimai/reason/undefined.rb +2 -3
- data/lib/sisimai/reason/userunknown.rb +129 -146
- data/lib/sisimai/reason/vacation.rb +3 -4
- data/lib/sisimai/reason/virusdetected.rb +10 -11
- data/lib/sisimai/reason.rb +59 -64
- data/lib/sisimai/rfc1894.rb +55 -28
- data/lib/sisimai/rfc2045.rb +373 -0
- data/lib/sisimai/rfc3464.rb +250 -308
- data/lib/sisimai/rfc3834.rb +42 -45
- data/lib/sisimai/rfc5322.rb +75 -100
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +42 -40
- data/lib/sisimai/rhost/nttdocomo.rb +12 -12
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +504 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +35 -21
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +42 -22
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
module Sisimai
|
2
|
+
# Sisimai::RFC2045 is a MIME Utilities for Sisimai.
|
3
|
+
module RFC2045
|
4
|
+
class << self
|
5
|
+
require 'base64'
|
6
|
+
require 'sisimai/string'
|
7
|
+
|
8
|
+
# Check that the argument is MIME-Encoded string or not
|
9
|
+
# @param [String] argvs String to be checked
|
10
|
+
# @return [True,False] false: Not MIME encoded string
|
11
|
+
# true: MIME encoded string
|
12
|
+
def is_encoded(argv1)
|
13
|
+
return nil unless argv1
|
14
|
+
|
15
|
+
text1 = argv1.delete('"')
|
16
|
+
mime1 = false
|
17
|
+
piece = []
|
18
|
+
|
19
|
+
if text1.include?(' ')
|
20
|
+
# Multiple MIME-Encoded strings in a line
|
21
|
+
piece = text1.split(' ')
|
22
|
+
else
|
23
|
+
piece << text1
|
24
|
+
end
|
25
|
+
|
26
|
+
while e = piece.shift do
|
27
|
+
# Check all the string in the array
|
28
|
+
next unless e =~ /[ \t]*=[?][-_0-9A-Za-z]+[?][BbQq][?].+[?]=?[ \t]*/
|
29
|
+
mime1 = true
|
30
|
+
end
|
31
|
+
return mime1
|
32
|
+
end
|
33
|
+
|
34
|
+
# Decode MIME-Encoded string in an email header
|
35
|
+
# @param [Array] argvs An array including MIME-Encoded text
|
36
|
+
# @return [String] MIME-Decoded text
|
37
|
+
def decodeH(argvs = [])
|
38
|
+
ctxcharset = nil
|
39
|
+
qbencoding = nil
|
40
|
+
textblocks = []
|
41
|
+
|
42
|
+
while e = argvs.shift do
|
43
|
+
# Check and decode each element
|
44
|
+
e = e.strip.delete('"')
|
45
|
+
|
46
|
+
if self.is_encoded(e)
|
47
|
+
# MIME Encoded string like "=?utf-8?B?55m954yr44Gr44KD44KT44GT?="
|
48
|
+
next unless cv = e.match(/\A(.*)=[?]([-_0-9A-Za-z]+)[?]([BbQq])[?](.+)[?]=?(.*)\z/)
|
49
|
+
|
50
|
+
ctxcharset ||= cv[2]
|
51
|
+
qbencoding ||= cv[3]
|
52
|
+
notdecoded = cv[4]
|
53
|
+
|
54
|
+
textblocks << cv[1]
|
55
|
+
textblocks << if qbencoding.upcase == 'B'
|
56
|
+
Base64.decode64(notdecoded)
|
57
|
+
else
|
58
|
+
notdecoded.unpack('M').first
|
59
|
+
end
|
60
|
+
textblocks[-1].gsub!(/\r\n/, '')
|
61
|
+
textblocks << cv[5]
|
62
|
+
else
|
63
|
+
textblocks << if textblocks.empty? then e else ' ' << e end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return '' if textblocks.empty?
|
68
|
+
p = textblocks.join('')
|
69
|
+
|
70
|
+
if ctxcharset && qbencoding
|
71
|
+
# utf8 => UTF-8
|
72
|
+
ctxcharset = 'UTF-8' if ctxcharset.casecmp('UTF8') == 0
|
73
|
+
|
74
|
+
unless ctxcharset.casecmp('UTF-8') == 0
|
75
|
+
# Characterset is not UTF-8
|
76
|
+
begin
|
77
|
+
p .encode!('UTF-8', ctxcharset)
|
78
|
+
rescue
|
79
|
+
p = 'FAILED TO CONVERT THE SUBJECT'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return p.force_encoding('UTF-8').scrub('?')
|
85
|
+
end
|
86
|
+
|
87
|
+
# Decode MIME BASE64 Encoded string
|
88
|
+
# @param [String] argv0 MIME Encoded text
|
89
|
+
# @return [String] MIME-Decoded text
|
90
|
+
def decodeB(argv0 = nil)
|
91
|
+
return nil unless argv0
|
92
|
+
|
93
|
+
p = nil
|
94
|
+
if cv = argv0.match(%r|([+/\=0-9A-Za-z\r\n]+)|) then p = Base64.decode64(cv[1]) end
|
95
|
+
return p ? p.scrub('?') : nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Decode MIME Quoted-Printable Encoded string
|
99
|
+
# @param [String] argv0 MIME Encoded text
|
100
|
+
# @return [String] MIME Decoded text
|
101
|
+
def decodeQ(argv0 = nil)
|
102
|
+
return nil unless argv0
|
103
|
+
return argv0.unpack('M').first.scrub('?')
|
104
|
+
end
|
105
|
+
|
106
|
+
# Find a value of specified field name from Content-Type: header
|
107
|
+
# @param [String] argv0 The value of Content-Type: header
|
108
|
+
# @param [String] argv1 Lower-cased attribute name of the parameter
|
109
|
+
# @return [String] The value of the parameter
|
110
|
+
# @since v5.0.0
|
111
|
+
def parameter(argv0 = '', argv1 = '')
|
112
|
+
return nil if argv0.empty?
|
113
|
+
parameterq = argv1.size > 0 ? argv1 + '=' : ''
|
114
|
+
paramindex = argv1.size > 0 ? argv0.index(parameterq) : 0
|
115
|
+
return '' unless paramindex
|
116
|
+
|
117
|
+
# Find the value of the parameter name specified in argv1
|
118
|
+
foundtoken = argv0[paramindex + parameterq.size, argv0.size].split(';', 2)[0] || ''
|
119
|
+
foundtoken = foundtoken.downcase unless argv1 == 'boundary'
|
120
|
+
foundtoken = foundtoken.delete('"').delete("'")
|
121
|
+
return foundtoken
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get a boundary string
|
125
|
+
# @param [String] argv0 The value of Content-Type header
|
126
|
+
# @param [Integer] start -1: boundary string itself
|
127
|
+
# 0: Start of boundary
|
128
|
+
# 1: End of boundary
|
129
|
+
# @return [String] Boundary string
|
130
|
+
def boundary(argv0 = '', start = -1)
|
131
|
+
return nil if argv0.empty?
|
132
|
+
btext = parameter(argv0, 'boundary')
|
133
|
+
return '' if btext.empty?
|
134
|
+
|
135
|
+
# Content-Type: multipart/mixed; boundary=Apple-Mail-5--931376066
|
136
|
+
# Content-Type: multipart/report; report-type=delivery-status;
|
137
|
+
# boundary="n6H9lKZh014511.1247824040/mx.example.jp"
|
138
|
+
btext = '--' + btext if start > -1
|
139
|
+
btext = btext + '--' if start > 0
|
140
|
+
return btext
|
141
|
+
end
|
142
|
+
|
143
|
+
# Cut header fields except Content-Type, Content-Transfer-Encoding from multipart/* block
|
144
|
+
# @param [String] block multipart/* block text
|
145
|
+
# @param [Boolean] heads true = Returns only Content-(Type|Transfer-Encoding) headers
|
146
|
+
# @return [Array] Two headers and body part of multipart/* block
|
147
|
+
# @since v5.0.0
|
148
|
+
def haircut(block = '', heads = false)
|
149
|
+
return nil if block.empty?
|
150
|
+
|
151
|
+
(upperchunk, lowerchunk) = block.split("\n\n", 2)
|
152
|
+
return ['', ''] if upperchunk.to_s.empty?
|
153
|
+
return ['', ''] unless upperchunk.index('Content-Type')
|
154
|
+
|
155
|
+
headerpart = ['', ''] # ["text/plain; charset=iso-2022-jp; ...", "quoted-printable"]
|
156
|
+
multipart1 = [] # [headerpart, "body"]
|
157
|
+
|
158
|
+
upperchunk.split("\n").each do |e|
|
159
|
+
# Remove fields except Content-Type:, and Content-Transfer-Encoding: in each part of
|
160
|
+
# multipart/* block such as the following:
|
161
|
+
# Date: Thu, 29 Apr 2018 22:22:22 +0900
|
162
|
+
# MIME-Version: 1.0
|
163
|
+
# Message-ID: ...
|
164
|
+
# Content-Transfer-Encoding: quoted-printable
|
165
|
+
# Content-Type: text/plain; charset=us-ascii
|
166
|
+
if e.index('Content-Type:') == 0
|
167
|
+
# Content-Type: ***
|
168
|
+
v = e.split(' ', 2)[-1]
|
169
|
+
headerpart[0] = v.index('boundary=') ? v : v.downcase
|
170
|
+
|
171
|
+
elsif e.index('Content-Transfer-Encoding:') == 0
|
172
|
+
# Content-Transfer-Encoding: ***
|
173
|
+
headerpart[1] = e.split(' ', 2)[-1].downcase
|
174
|
+
|
175
|
+
elsif e.index('boundary=') || e.index('charset=')
|
176
|
+
# "Content-Type" field has boundary="..." or charset="utf-8"
|
177
|
+
next if headerpart[0].empty?
|
178
|
+
headerpart[0] << " " << e
|
179
|
+
headerpart[0].gsub!(/\s\s+/, ' ')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
return headerpart if heads
|
183
|
+
|
184
|
+
mediatypev = headerpart[0].downcase
|
185
|
+
ctencoding = headerpart[1]
|
186
|
+
multipart1 = headerpart << ''
|
187
|
+
|
188
|
+
while true do
|
189
|
+
# Check the upper block: Make a body part at the 2nd element of multipart1
|
190
|
+
multipart1[2] = sprintf("Content-Type: %s\n", headerpart[0])
|
191
|
+
|
192
|
+
# Do not append Content-Transfer-Encoding: header when the part is the original message:
|
193
|
+
# Content-Type is message/rfc822 or text/rfc822-headers, or message/delivery-status, or
|
194
|
+
# message/feedback-report
|
195
|
+
break if mediatypev.index('/rfc822')
|
196
|
+
break if mediatypev.index('/delivery-status')
|
197
|
+
break if mediatypev.index('/feedback-report')
|
198
|
+
break if ctencoding.empty?
|
199
|
+
|
200
|
+
multipart1[2] << sprintf("Content-Transfer-Encoding: %s\n", ctencoding)
|
201
|
+
break
|
202
|
+
end
|
203
|
+
|
204
|
+
while true do
|
205
|
+
# Append LF before the lower chunk into the 2nd element of multipart1
|
206
|
+
break if lowerchunk.empty?
|
207
|
+
break if lowerchunk[0, 1] == "\n"
|
208
|
+
|
209
|
+
multipart1[2] << "\n"
|
210
|
+
break
|
211
|
+
end
|
212
|
+
multipart1[2] << lowerchunk
|
213
|
+
return multipart1
|
214
|
+
end
|
215
|
+
|
216
|
+
# Split argv1: multipart/* blocks by a boundary string in argv0
|
217
|
+
# @param [String] argv0 The value of Content-Type header
|
218
|
+
# @param [String] argv1 A pointer to multipart/* message blocks
|
219
|
+
# @return [Array] List of each part of multipart/*
|
220
|
+
# @since v5.0.0
|
221
|
+
def levelout(argv0 = '', argv1 = '')
|
222
|
+
return [] if argv0.empty?
|
223
|
+
return [] if argv1.empty?
|
224
|
+
|
225
|
+
boundary01 = boundary(argv0, 0); return [] if boundary01.empty?
|
226
|
+
multiparts = argv1.split(Regexp.new(Regexp.escape(boundary01) + "\n"))
|
227
|
+
partstable = []
|
228
|
+
|
229
|
+
multiparts.shift if multiparts[0].size < 8
|
230
|
+
multiparts.pop if multiparts[-1].size < 8
|
231
|
+
|
232
|
+
while e = multiparts.shift do
|
233
|
+
# Check each part and breaks up internal multipart/* block
|
234
|
+
f = haircut(e)
|
235
|
+
if f[0].index('multipart/')
|
236
|
+
# There is nested multipart/* block
|
237
|
+
boundary02 = boundary(f[0], -1); next if boundary02.empty?
|
238
|
+
bodyinside = f[-1].split("\n\n", 2)[-1]
|
239
|
+
next unless bodyinside.size > 8
|
240
|
+
next unless bodyinside.index(boundary02)
|
241
|
+
|
242
|
+
v = levelout(f[0], bodyinside)
|
243
|
+
partstable += v if v.size > 0
|
244
|
+
else
|
245
|
+
# The part is not a multipart/* block
|
246
|
+
b = f[-1].size > 0 ? f[-1] : e
|
247
|
+
v = [f[0], f[1], f[0].size > 0 ? b.split("\n\n", 2)[-1] : b]
|
248
|
+
partstable << v
|
249
|
+
end
|
250
|
+
end
|
251
|
+
return [] if partstable.empty?
|
252
|
+
|
253
|
+
# Remove $boundary01.'--' and strings from the boundary to the end of the body part.
|
254
|
+
boundary01.chomp!
|
255
|
+
b = partstable[-1][2]
|
256
|
+
p = b.index(boundary01 + '--')
|
257
|
+
b[p, b.size] = "" if p
|
258
|
+
|
259
|
+
return partstable
|
260
|
+
end
|
261
|
+
|
262
|
+
# Make flat multipart/* part blocks and decode
|
263
|
+
# @param [String] argv0 The value of Content-Type header
|
264
|
+
# @param [String] argv1 A pointer to multipart/* message blocks
|
265
|
+
# @return [String] Message body
|
266
|
+
def makeflat(argv0 = '', argv1 = '')
|
267
|
+
return nil unless argv0
|
268
|
+
return nil unless argv1
|
269
|
+
return '' unless argv0.index('multipart/')
|
270
|
+
return '' unless argv0.index('boundary=')
|
271
|
+
|
272
|
+
# Some bounce messages include lower-cased "content-type:" field such as the followings:
|
273
|
+
# - content-type: message/delivery-status => Content-Type: message/delivery-status
|
274
|
+
# - content-transfer-encoding: quoted-printable => Content-Transfer-Encoding: quoted-printable
|
275
|
+
# - CHARSET=, BOUNDARY= => charset-, boundary=
|
276
|
+
# - message/xdelivery-status => message/delivery-status
|
277
|
+
iso2022set = %r/charset=["']?(iso-2022-[-a-z0-9]+)['"]?\b/
|
278
|
+
multiparts = levelout(argv0, argv1)
|
279
|
+
flattenout = ''
|
280
|
+
|
281
|
+
while e = multiparts.shift do
|
282
|
+
# Pick only the following parts Sisimai::Lhost will use, and decode each part
|
283
|
+
# - text/plain, text/rfc822-headers
|
284
|
+
# - message/delivery-status, message/rfc822, message/partial, message/feedback-report
|
285
|
+
istexthtml = false
|
286
|
+
mediatypev = parameter(e[0]) || 'text/plain';
|
287
|
+
next if mediatypev.start_with?('text/', 'message/') == false
|
288
|
+
|
289
|
+
if mediatypev == 'text/html'
|
290
|
+
# Skip text/html part when the value of Content-Type: header in an internal part of
|
291
|
+
# multipart/* includes multipart/alternative;
|
292
|
+
next if argv0.index('multipart/alternative')
|
293
|
+
istexthtml = true
|
294
|
+
end
|
295
|
+
|
296
|
+
ctencoding = e[1]
|
297
|
+
bodyinside = e[2]
|
298
|
+
bodystring = ''
|
299
|
+
|
300
|
+
if ctencoding.size > 0
|
301
|
+
# Check the value of Content-Transfer-Encoding: header
|
302
|
+
if ctencoding == 'base64'
|
303
|
+
# Content-Transfer-Encoding: base64
|
304
|
+
bodystring = decodeB(bodyinside) || ''
|
305
|
+
|
306
|
+
elsif ctencoding == 'quoted-printable'
|
307
|
+
# Content-Transfer-Encoding: quoted-printable
|
308
|
+
bodystring = decodeQ(bodyinside) || ''
|
309
|
+
|
310
|
+
elsif ctencoding == '7bit'
|
311
|
+
# Content-Transfer-Encoding: 7bit
|
312
|
+
if cv = e[0].downcase.match(iso2022set)
|
313
|
+
# Content-Type: text/plain; charset=ISO-2022-JP
|
314
|
+
bodystring = Sisimai::String.to_utf8(bodyinside, cv[1]) || ''
|
315
|
+
else
|
316
|
+
# No "charset" parameter in the value of Content-Type: header
|
317
|
+
bodystring = bodyinside
|
318
|
+
end
|
319
|
+
else
|
320
|
+
# Content-Transfer-Encoding: 8bit, binary, and so on
|
321
|
+
bodystring = bodyinside
|
322
|
+
end
|
323
|
+
|
324
|
+
if istexthtml
|
325
|
+
# Try to delete HTML tags inside of text/html part whenever possible
|
326
|
+
bodystring = Sisimai::String.to_plain(bodystring) || ''
|
327
|
+
end
|
328
|
+
next if bodystring.empty?
|
329
|
+
|
330
|
+
# The body string will be encoded to UTF-8 forcely and call String#scrub method to avoid
|
331
|
+
# the following errors:
|
332
|
+
# - incompatible character encodings: ASCII-8BIT and UTF-8
|
333
|
+
# - invalid byte sequence in UTF-8
|
334
|
+
unless bodystring.encoding.to_s == 'UTF-8'
|
335
|
+
# ASCII-8BIT or other 8bit encodings
|
336
|
+
ctxcharset = parameter(e[0], 'charset')
|
337
|
+
if ctxcharset.empty?
|
338
|
+
# The part which has no "charset" parameter causes an ArgumentError: invalid byte
|
339
|
+
# sequence in UTF-8 so String#scrub should be called
|
340
|
+
bodystring.scrub!('?')
|
341
|
+
else
|
342
|
+
# ISO-8859-1, GB2312, and so on
|
343
|
+
bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset) || ''
|
344
|
+
end
|
345
|
+
bodystring << "\n\n"
|
346
|
+
end
|
347
|
+
|
348
|
+
bodystring.gsub!(/\r\n/, "\n") if bodystring.include?("\r\n") # Convert CRLF to LF
|
349
|
+
|
350
|
+
else
|
351
|
+
# There is no Content-Transfer-Encoding header in the part
|
352
|
+
bodystring << bodyinside
|
353
|
+
end
|
354
|
+
|
355
|
+
if mediatypev.include?('/delivery-status') || mediatypev.include?('/feedback-report') || mediatypev.include?('/rfc822')
|
356
|
+
# Add Content-Type: header of each part (will be used as a delimiter at Sisimai::Lhost)
|
357
|
+
# into the body inside when the value of Content-Type: field is message/delivery-status,
|
358
|
+
# message/rfc822, or text/rfc822-headers
|
359
|
+
bodystring = sprintf("Content-Type: %s\n%s", mediatypev, bodystring)
|
360
|
+
end
|
361
|
+
|
362
|
+
# Append "\n" when the last character of $bodystring is not LF
|
363
|
+
bodystring << "\n\n" unless bodystring[-2, 2] == "\n\n"
|
364
|
+
flattenout << bodystring
|
365
|
+
end
|
366
|
+
|
367
|
+
return flattenout
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|