sisimai 4.25.17-java → 5.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +406 -407
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +12 -12
- data/README-JA.md +142 -94
- data/README.md +282 -150
- 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 +20 -16
- 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 -325
- 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 +13 -18
- 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 -23
- 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/mac/reported-from-nick4tech-san-01.eml +0 -6
- /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
|
+
|