mime 0.4.2 → 0.4.3
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 +4 -4
- data/README.rdoc +14 -10
- data/Rakefile +9 -0
- data/lib/mime.rb +1 -1
- data/lib/mime/composite_media.rb +23 -4
- data/test/scaffold/multipart_alternative_related.msg +0 -0
- data/test/scaffold/multipart_related.msg +0 -0
- data/test/scaffold/rfc822_composite.msg +0 -0
- data/test/test_mime.rb +24 -2
- data/test/test_mime.rb-try +616 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 521f5a6513abe39c177b7a104f3daadffe034fe9
|
4
|
+
data.tar.gz: f86cd41fcd40b2d0874c191743e917ded58d85a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5131353ba887c7aac163d87147fefac233eabc4e3f6ab9b65a3b746c45d4352739f6d4ce51ecbdcf3c9cee4e3273083836babc45d8b3f2d6c12f524498679c21
|
7
|
+
data.tar.gz: 33782e5a37696c00faf723de25cfd6afc05542e95c08ccf86e2707eb77f400ba6fd85baa8d8095ec372542898c3aedbce555149db03bb2d368058aa67599eb9d
|
data/README.rdoc
CHANGED
@@ -217,22 +217,18 @@ Notice the _src_ of the _img_ tag.
|
|
217
217
|
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
218
218
|
image.transfer_encoding = 'binary' # could base64 encode the image instead
|
219
219
|
|
220
|
-
html_msg = Text.new(<<EOF, 'html', 'charset' => '
|
220
|
+
html_msg = Text.new(<<EOF, 'html', 'charset' => 'utf-8')
|
221
221
|
<html>
|
222
222
|
<body>
|
223
|
-
<h1>Ruby
|
224
|
-
<
|
225
|
-
Check out this cool pic.
|
226
|
-
<img alt="ruby is cool" src="cid:#{image.id}">
|
227
|
-
</p>
|
228
|
-
<p>Wasn't it cool?</p>
|
223
|
+
<h1>A Shiny Red Ruby</h1>
|
224
|
+
<img alt="a ruby gem" src="cid:#{image.id}">
|
229
225
|
</body>
|
230
226
|
</html>
|
231
227
|
EOF
|
232
228
|
|
233
229
|
related_msg = Multipart::Related.new
|
234
|
-
related_msg.add(html_msg)
|
235
|
-
related_msg.inline(image)
|
230
|
+
related_msg.add(html_msg) # first entity added becomes the root object
|
231
|
+
related_msg.inline(image) # the root object references the inline image
|
236
232
|
|
237
233
|
email_msg = Mail.new(related_msg)
|
238
234
|
email_msg.to = 'jane@example.com'
|
@@ -373,7 +369,15 @@ Validators :: Please check *all* of your messages using the following lint
|
|
373
369
|
* Add bug tracker URL.
|
374
370
|
* Link to referenced commit messages.
|
375
371
|
6. 2014-06-13, v0.4.2
|
376
|
-
*
|
372
|
+
* FIX: remove header field when set to nil.
|
373
|
+
* RFC compliance: set Sender field to first From address if multiple From
|
374
|
+
addresses and the Sender header is not already set.
|
375
|
+
* Improve TextFlowed tests.
|
376
|
+
* Add TODO.mkd.
|
377
|
+
* FIX: Ruby 1.8.7 compatibility issue.
|
378
|
+
7. 2015-11-05, v0.4.3
|
379
|
+
* FIX: Add "type" parameter to Multipart/Related messages (RFC conformance).
|
380
|
+
* FIX: Documention bug regarding Multipart/Alternative entity order.
|
377
381
|
|
378
382
|
|
379
383
|
== License
|
data/Rakefile
CHANGED
@@ -16,3 +16,12 @@ end
|
|
16
16
|
Rake::TestTask.new do |t|
|
17
17
|
t.warning = true
|
18
18
|
end
|
19
|
+
|
20
|
+
desc 'Upload RDoc HTML files to server'
|
21
|
+
task :upload_rdoc => :rdoc do
|
22
|
+
%x[
|
23
|
+
rdir=/var/www/sites/ecentryx.com/public/gems/mime
|
24
|
+
rsvr=hercules
|
25
|
+
cd ./doc && pax -w . | ssh $rsvr "cd $rdir && rm -rf ./*; pax -r"
|
26
|
+
]
|
27
|
+
end
|
data/lib/mime.rb
CHANGED
data/lib/mime/composite_media.rb
CHANGED
@@ -67,7 +67,7 @@ module MIME
|
|
67
67
|
# msg.add(MIME::Text.new('<html>html text</html>', 'html'))
|
68
68
|
#
|
69
69
|
# The order in which the entities are added is significant. Add the simplest
|
70
|
-
# representations first.
|
70
|
+
# representations first or in increasing order of complexity.
|
71
71
|
#
|
72
72
|
def add entity
|
73
73
|
raise Error.new('can only add Media objects') unless entity.is_a? Media
|
@@ -143,9 +143,13 @@ module MIME
|
|
143
143
|
|
144
144
|
#
|
145
145
|
# The Alternative subtype indicates that each contained entity is an
|
146
|
-
# alternatively formatted version of the same content.
|
147
|
-
#
|
148
|
-
#
|
146
|
+
# alternatively formatted version of the same content.
|
147
|
+
#
|
148
|
+
# In general, when composing multipart/alternative entities, add the body
|
149
|
+
# parts in increasing order of preference; that is, with the preferred format
|
150
|
+
# last. For example, to prioritize for fancy text, add the plainest format
|
151
|
+
# first and the richest format last. Typically, receiving user agents select
|
152
|
+
# the last format they are capable of displaying or interpreting.
|
149
153
|
#
|
150
154
|
class Multipart::Alternative < Multipart
|
151
155
|
|
@@ -225,6 +229,21 @@ module MIME
|
|
225
229
|
super('related')
|
226
230
|
end
|
227
231
|
|
232
|
+
#
|
233
|
+
# The first entity added becomes the root object. The related message
|
234
|
+
# type is set to the value of the root object media type.
|
235
|
+
#--
|
236
|
+
# The "type" parameter is required and should equal the root media type.
|
237
|
+
# https://tools.ietf.org/html/rfc2387#section-3.1
|
238
|
+
#
|
239
|
+
def add entity
|
240
|
+
unless type.include? '; type='
|
241
|
+
root_type = entity.type.partition(';').first # omit parameters
|
242
|
+
self.type = append_field_params(type, 'type' => root_type)
|
243
|
+
end
|
244
|
+
super
|
245
|
+
end
|
246
|
+
|
228
247
|
end
|
229
248
|
|
230
249
|
end
|
Binary file
|
Binary file
|
Binary file
|
data/test/test_mime.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
gem 'minitest' # minitest in 1.9 stdlib is crufty
|
2
1
|
require 'minitest/autorun'
|
3
2
|
require 'mime'
|
4
3
|
|
5
|
-
Encoding.default_external = 'ASCII-8BIT'
|
4
|
+
Encoding.default_external = 'ASCII-8BIT'
|
6
5
|
|
7
6
|
|
8
7
|
class MIMETest < Minitest::Test
|
@@ -319,6 +318,29 @@ EOF
|
|
319
318
|
assert_equal sender, msg.sender
|
320
319
|
end
|
321
320
|
|
321
|
+
# Related type parameter: https://tools.ietf.org/html/rfc2387#section-3.1
|
322
|
+
def test_related_message_inherits_type_from_root
|
323
|
+
msg = MIME::Multipart::Related.new
|
324
|
+
refute_includes(msg.type, 'type')
|
325
|
+
msg.add(MIME::Text.new('body', 'plain')) # Root object (first entity)
|
326
|
+
assert_includes(msg.type, 'type=text/plain') # sets the related msg type.
|
327
|
+
msg.add(MIME::Text.new('body', 'html')) # Additional entities have
|
328
|
+
refute_includes(msg.type, 'type=text/html') # no effect on msg type.
|
329
|
+
assert_includes(msg.type, 'type=text/plain')
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_related_message_inherits_parameterized_type_from_root
|
333
|
+
msg = MIME::Multipart::Related.new
|
334
|
+
refute_includes(msg.type, 'type')
|
335
|
+
|
336
|
+
root = MIME::Text.new('a message', 'plain', 'charset' => 'us-ascii')
|
337
|
+
assert_equal(root.type, 'text/plain; charset=us-ascii')
|
338
|
+
|
339
|
+
msg.add(root)
|
340
|
+
assert_includes(msg.type, 'type=text/plain') # Inherits root mime type,
|
341
|
+
refute_includes(msg.type, 'charset=us-ascii') # but params are omitted.
|
342
|
+
end
|
343
|
+
|
322
344
|
def test_rfc2822_date_format
|
323
345
|
msg = MIME::Mail.new
|
324
346
|
assert_kind_of(Time, msg.date)
|
@@ -0,0 +1,616 @@
|
|
1
|
+
# THIS IS STILL HERE BECAUSE A LOT OF WORK WENT INTO MAKING TESTS AGNOSTIC OF MIME HEADER ORDER.
|
2
|
+
# HOWEVER, THINGS GOT MESSY SO REVERTED BACK TO COMPARING AGAINST KNOWN GOOD MESSAGES.
|
3
|
+
# THIS IS HERE JUST IN CASE. IN THE FUTURE, REMOVE IT IF THERE IS NO NEED FOR IT.
|
4
|
+
|
5
|
+
gem 'minitest' # minitest in 1.9 stdlib is crufty
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'mime'
|
8
|
+
|
9
|
+
# may be able to remove in 2.0
|
10
|
+
Encoding.default_external = 'ASCII-8BIT'
|
11
|
+
|
12
|
+
class MIMETest < Minitest::Test
|
13
|
+
|
14
|
+
CRLF = "\r\n"
|
15
|
+
HDR_BDY_SEP = CRLF * 2
|
16
|
+
MESSAGE_ID = /^Message-ID: <\d+@\d+>\r\n/
|
17
|
+
CONTENT_ID = /^Content-ID: <\d+\.\d+>\r\n/
|
18
|
+
|
19
|
+
CDISPOSITION = /^Content-Disposition: .*\r\n/
|
20
|
+
CTYPE = /^Content-Type: .*\r\n/
|
21
|
+
|
22
|
+
DATE = /^Date: ..., \d{1,2} ... \d{4} \d\d:\d\d:\d\d -\d{4}\r\n/
|
23
|
+
VERSION = /^MIME-Version: 1.0 \(Ruby MIME v\d\.\d\)\r\n/
|
24
|
+
#BOUNDARY = /^--Boundary_\d+\.\d+\r\n/
|
25
|
+
BOUNDARY = /^--Boundary_\d+\.\d+(--)?\r\n/
|
26
|
+
BOUNDARY_LAST = /^--Boundary_\d+\.\d+--\r\n/
|
27
|
+
CID = /cid:\d+\.\d+/
|
28
|
+
|
29
|
+
CTYPE_TEXT_PLAIN = /^Content-Type: text\/plain; charset=us-ascii\r\n/
|
30
|
+
CTYPE_TEXT_HTML = /^Content-Type: text\/html\r\n/
|
31
|
+
CTYPE_TEXT_XML = /^Content-Type: text\/xml\r\n/
|
32
|
+
CTYPE_IMAGE_JPEG = /^Content-Type: image\/jpeg\r\n/
|
33
|
+
CTYPE_IMAGE_PNG = /^Content-Type: image\/png\r\n/
|
34
|
+
CTYPE_VIDEO_MPEG = /^Content-Type: video\/mpeg\r\n/
|
35
|
+
CTYPE_AUDIO_MIDI = /^Content-Type: audio\/midi\r\n/
|
36
|
+
CTYPE_APPLICATION = /^Content-Type: application\/octet-stream\r\n/
|
37
|
+
|
38
|
+
CTYPE_MPART_FORM = /^Content-Type: multipart\/form-data; boundary=Boundary_\d+\.\d+\r\n/
|
39
|
+
CTYPE_MPART_ALT = /^Content-Type: multipart\/alternative; boundary=Boundary_\d+\.\d+\r\n/
|
40
|
+
CTYPE_MPART_MIXED = /^Content-Type: multipart\/mixed; boundary=Boundary_\d+\.\d+\r\n/
|
41
|
+
|
42
|
+
XFER_ENC_BINARY = /^Content-Transfer-Encoding: binary\r\n/
|
43
|
+
XFER_ENC_8BIT = /^Content-Transfer-Encoding: 8bit\r\n/
|
44
|
+
|
45
|
+
BINARY_DATA = '0110000101110101011001000110100101101111'
|
46
|
+
|
47
|
+
def test_make_top_level_rfc2822_message
|
48
|
+
rfc2822_msg = MIME::Message.new
|
49
|
+
rfc2822_msg.body = "\r\nmessage body"
|
50
|
+
msg = rfc2822_msg.to_s
|
51
|
+
|
52
|
+
assert_match MESSAGE_ID, msg
|
53
|
+
assert_match DATE, msg
|
54
|
+
assert_match VERSION, msg
|
55
|
+
assert_equal_num_headers 3, msg
|
56
|
+
assert_equal_body "message body", msg
|
57
|
+
end
|
58
|
+
|
59
|
+
# TODO remove audio.msg
|
60
|
+
def test_make_audio_message
|
61
|
+
audio_media = MIME::AudioMedia.new(BINARY_DATA, 'audio/midi')
|
62
|
+
audio_media.content_transfer_encoding = 'binary'
|
63
|
+
msg = MIME::Message.new(audio_media).to_s
|
64
|
+
|
65
|
+
[CONTENT_ID, XFER_ENC_BINARY, CTYPE_AUDIO_MIDI].each do |header|
|
66
|
+
assert_match header, msg
|
67
|
+
end
|
68
|
+
assert_equal_num_headers 6, msg
|
69
|
+
assert_equal_body BINARY_DATA, msg
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO remove expected_mime_msg = IO.read(sd('/application.msg'))
|
73
|
+
def test_make_application_message
|
74
|
+
application_media = MIME::ApplicationMedia.new(BINARY_DATA)
|
75
|
+
application_media.content_transfer_encoding = 'binary'
|
76
|
+
msg = MIME::Message.new(application_media).to_s
|
77
|
+
|
78
|
+
[CONTENT_ID, XFER_ENC_BINARY, CTYPE_APPLICATION].each do |header|
|
79
|
+
assert_match header, msg
|
80
|
+
end
|
81
|
+
assert_equal_num_headers 6, msg
|
82
|
+
assert_equal_body BINARY_DATA, msg
|
83
|
+
end
|
84
|
+
|
85
|
+
# TODO remove image.msg
|
86
|
+
def test_make_image_message
|
87
|
+
image = IO.read(sd('/image.jpg'))
|
88
|
+
image_media = MIME::ImageMedia.new(image)
|
89
|
+
image_media.content_type = 'image/jpeg'
|
90
|
+
image_media.content_transfer_encoding = 'binary'
|
91
|
+
msg = MIME::Message.new(image_media).to_s
|
92
|
+
|
93
|
+
[CONTENT_ID, XFER_ENC_BINARY, CTYPE_IMAGE_JPEG].each do |header|
|
94
|
+
assert_match header, msg
|
95
|
+
end
|
96
|
+
|
97
|
+
assert_equal_num_headers 6, msg
|
98
|
+
assert_equal_body image, msg
|
99
|
+
end
|
100
|
+
|
101
|
+
# TODO remove text.msg
|
102
|
+
def test_make_text_message
|
103
|
+
body = 'a plain text message'
|
104
|
+
msg = MIME::Message.new(MIME::TextMedia.new(body)).to_s
|
105
|
+
|
106
|
+
[CTYPE_TEXT_PLAIN].each do |header|
|
107
|
+
assert_match header, msg
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# TODO remove video.msg
|
112
|
+
def test_make_video_message
|
113
|
+
video_media = MIME::VideoMedia.new(BINARY_DATA)
|
114
|
+
video_media.content_type = 'video/mpeg'
|
115
|
+
video_media.content_transfer_encoding = 'binary'
|
116
|
+
msg = MIME::Message.new(video_media).to_s
|
117
|
+
|
118
|
+
[CONTENT_ID, XFER_ENC_BINARY, CTYPE_VIDEO_MPEG].each do |header|
|
119
|
+
assert_match header, msg
|
120
|
+
end
|
121
|
+
assert_equal_num_headers 6, msg
|
122
|
+
assert_equal_body BINARY_DATA, msg
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_no_instantiation_of_abstract_classes
|
126
|
+
e = MIME::AbstractClassError
|
127
|
+
assert_raises(e) {MIME::MediaType.new(nil, nil)}
|
128
|
+
assert_raises(e) {MIME::DiscreteMediaType.new(nil)}
|
129
|
+
assert_raises(e) {MIME::CompositeMediaType.new(nil)}
|
130
|
+
assert_raises(e) {MIME::MultipartMedia.new(nil)}
|
131
|
+
end
|
132
|
+
|
133
|
+
# def test_boundaries
|
134
|
+
# # CREATE a multipart message; take simplified version of next form data
|
135
|
+
# # test
|
136
|
+
# e = msg.scan(BOUNDARY).each
|
137
|
+
# first_boundary = e.next
|
138
|
+
# assert_equal first_boundary, e.next
|
139
|
+
# assert_equal first_boundary, e.next
|
140
|
+
# last_boundary = e.next
|
141
|
+
# refute_equal first_boundary, last_boundary
|
142
|
+
# assert_match /^--Boundary_\d+\.\d+\r\n/, first_boundary
|
143
|
+
# assert_match /^--Boundary_\d+\.\d+--\r\n/, last_boundary
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# def test_unique_content_ids_in_multipart_message
|
147
|
+
#
|
148
|
+
# content_ids = msg.scan(/^Content-ID: <(\d+.\d+)>/)
|
149
|
+
# assert_equal 4, content_ids.flatten.uniq.size # all IDs must be unique
|
150
|
+
# end
|
151
|
+
|
152
|
+
# TODO rm multipart_form_data_text.msg
|
153
|
+
def test_multipart_form_data_with_text_entities
|
154
|
+
txt_data = 'text body'
|
155
|
+
htm_data = IO.read(sd('data.htm'))
|
156
|
+
xml_data = IO.read(sd('data.xml'))
|
157
|
+
|
158
|
+
txt = MIME::TextMedia.new('text body')
|
159
|
+
htm = MIME::TextMedia.new(htm_data, 'text/html')
|
160
|
+
xml = MIME::TextMedia.new(xml_data, 'text/xml')
|
161
|
+
|
162
|
+
form_data = MIME::MultipartMedia::FormData.new
|
163
|
+
form_data.add_entity txt, 'txt'
|
164
|
+
form_data.add_entity htm, 'htm'
|
165
|
+
form_data.add_entity xml, 'xml'
|
166
|
+
msg = form_data.to_s
|
167
|
+
|
168
|
+
parts = msg.split(BOUNDARY)
|
169
|
+
assert_equal 5, parts.size, 'main header, 3 entities, last boundary'
|
170
|
+
assert_equal '--', parts.pop, 'remnant of last boundary marker'
|
171
|
+
|
172
|
+
# main header
|
173
|
+
assert_match CONTENT_ID, parts[0]
|
174
|
+
assert_match CTYPE_MPART_FORM, parts[0]
|
175
|
+
assert_equal_num_headers 2, parts[0]
|
176
|
+
assert_equal_body '', parts[0]
|
177
|
+
|
178
|
+
# xml entity
|
179
|
+
assert_match CONTENT_ID, parts[1]
|
180
|
+
assert_match CTYPE_TEXT_XML, parts[1]
|
181
|
+
assert_match /^Content-Disposition: form-data; name="xml"\r\n/, parts[1]
|
182
|
+
assert_equal_num_headers 3, parts[1]
|
183
|
+
assert_equal_body xml_data, parts[1]
|
184
|
+
|
185
|
+
# html entity
|
186
|
+
assert_match CONTENT_ID, parts[2]
|
187
|
+
assert_match CTYPE_TEXT_HTML, parts[2]
|
188
|
+
assert_match /^Content-Disposition: form-data; name="htm"\r\n/, parts[2]
|
189
|
+
assert_equal_num_headers 3, parts[2]
|
190
|
+
assert_equal_body htm_data, parts[2]
|
191
|
+
|
192
|
+
# text entity
|
193
|
+
assert_match CONTENT_ID, parts[3]
|
194
|
+
assert_match CTYPE_TEXT_PLAIN, parts[3]
|
195
|
+
assert_match /^Content-Disposition: form-data; name="txt"\r\n/, parts[3]
|
196
|
+
assert_equal_num_headers 3, parts[3]
|
197
|
+
assert_equal_body txt_data, parts[3]
|
198
|
+
end
|
199
|
+
|
200
|
+
# TODO test that only basename of the file is included in header
|
201
|
+
# test if no filename is used, then probably no filename param
|
202
|
+
# see next test for ideas
|
203
|
+
def test_content_disposition_filename
|
204
|
+
|
205
|
+
pass
|
206
|
+
end
|
207
|
+
|
208
|
+
# rm multipart_form_data_file.msg
|
209
|
+
# rm multipart_form_data_file_and_text.msg
|
210
|
+
def test_multipart_form_data_with_text_and_file_entities
|
211
|
+
img1_filename = 'image.jpg'
|
212
|
+
img2_filename = 'ruby.png'
|
213
|
+
img1_data = IO.read(sd(img1_filename))
|
214
|
+
img2_data = IO.read(sd(img2_filename))
|
215
|
+
img1 = MIME::ImageMedia.new(img1_data, 'image/jpeg')
|
216
|
+
img2 = MIME::ImageMedia.new(img2_data, 'image/png')
|
217
|
+
img1.content_transfer_encoding = '8bit'
|
218
|
+
img2.content_transfer_encoding = '8bit'
|
219
|
+
|
220
|
+
desc_data = 'This is plain text description of images.'
|
221
|
+
desc = MIME::TextMedia.new(desc_data)
|
222
|
+
|
223
|
+
form_data = MIME::MultipartMedia::FormData.new
|
224
|
+
form_data.add_entity desc, 'description'
|
225
|
+
form_data.add_entity img2, 'image_2', img2_filename
|
226
|
+
form_data.add_entity img1, 'image_1', img1_filename
|
227
|
+
msg = form_data.to_s
|
228
|
+
|
229
|
+
parts = msg.split(BOUNDARY)
|
230
|
+
assert_equal 5, parts.size, 'main header, 3 entities, last boundary'
|
231
|
+
assert_equal '--', parts.pop, 'remnant of last boundary marker'
|
232
|
+
|
233
|
+
# main header
|
234
|
+
assert_match CONTENT_ID, parts[0]
|
235
|
+
assert_match CTYPE_MPART_FORM, parts[0]
|
236
|
+
assert_equal_num_headers 2, parts[0]
|
237
|
+
assert_equal_body '', parts[0]
|
238
|
+
|
239
|
+
# first image entity
|
240
|
+
assert_match CONTENT_ID, parts[1]
|
241
|
+
assert_match CTYPE_IMAGE_JPEG, parts[1]
|
242
|
+
assert_match XFER_ENC_8BIT, parts[1]
|
243
|
+
assert_match /^Content-Disposition: form-data; name="image_1"; filename="#{img1_filename}"\r\n/, parts[1]
|
244
|
+
assert_equal_num_headers 4, parts[1]
|
245
|
+
assert_equal_body img1_data, parts[1]
|
246
|
+
|
247
|
+
# second image entity
|
248
|
+
assert_match CONTENT_ID, parts[2]
|
249
|
+
assert_match CTYPE_IMAGE_PNG, parts[2]
|
250
|
+
assert_match XFER_ENC_8BIT, parts[2]
|
251
|
+
assert_match /^Content-Disposition: form-data; name="image_2"; filename="#{img2_filename}"\r\n/, parts[2]
|
252
|
+
assert_equal_num_headers 4, parts[2]
|
253
|
+
assert_equal_body img2_data, parts[2]
|
254
|
+
|
255
|
+
# plain text entity
|
256
|
+
assert_match CONTENT_ID, parts[3]
|
257
|
+
assert_match CTYPE_TEXT_PLAIN, parts[3]
|
258
|
+
assert_match /^Content-Disposition: form-data; name="description"\r\n/, parts[3]
|
259
|
+
assert_equal_num_headers 3, parts[3]
|
260
|
+
assert_equal_body desc_data, parts[3]
|
261
|
+
end
|
262
|
+
|
263
|
+
# rm plain_text_email.msg
|
264
|
+
def test_construction_of_plain_text_email_message
|
265
|
+
subject = 'This is an important email'
|
266
|
+
body = 'This is the all important email body.'
|
267
|
+
|
268
|
+
email_msg = MIME::Message.new
|
269
|
+
email_msg.to = {
|
270
|
+
'john@example.com' => 'John',
|
271
|
+
'paul@example.com' => nil,
|
272
|
+
'mary@example.com' => 'Mary'
|
273
|
+
}
|
274
|
+
email_msg.cc = {'Head Honcho' => 'boss@example.com'}
|
275
|
+
email_msg.from = {'jane@example.com' => nil}
|
276
|
+
email_msg.subject = subject
|
277
|
+
email_msg.body = MIME::TextMedia.new(body)
|
278
|
+
|
279
|
+
msg = email_msg.to_s
|
280
|
+
|
281
|
+
assert_equal_num_headers 9, msg
|
282
|
+
assert_match MESSAGE_ID, msg
|
283
|
+
assert_match CONTENT_ID, msg
|
284
|
+
assert_match CTYPE_TEXT_PLAIN, msg
|
285
|
+
assert_match DATE, msg
|
286
|
+
assert_match VERSION, msg
|
287
|
+
assert_match /^Subject: #{subject}\r\n/, msg
|
288
|
+
assert_match /^From: jane@example.com\r\n/, msg
|
289
|
+
assert_match /^To: .+, .+, .+\r\n/, msg # 3 addresses
|
290
|
+
assert_match /^To: .*John <john@example.com>.*\r\n/, msg
|
291
|
+
assert_match /^To: .*Mary <mary@example.com>.*\r\n/, msg
|
292
|
+
assert_match /^To: .*paul@example.com.*\r\n/, msg
|
293
|
+
assert_match /^Cc: boss@example.com <Head Honcho>\r\n/, msg
|
294
|
+
assert_equal_body body, msg
|
295
|
+
end
|
296
|
+
|
297
|
+
def test_content_type_detection
|
298
|
+
(o = Object.new).extend(MIME::ContentTypes)
|
299
|
+
|
300
|
+
# test using file path, relative and absolute
|
301
|
+
assert_equal 'application/pdf', o.file_type('book.pdf')
|
302
|
+
assert_equal 'video/quicktime', o.file_type('mini.mov')
|
303
|
+
assert_equal 'application/octet-stream', o.file_type('dsk.iso')
|
304
|
+
assert_equal 'audio/mpeg', o.file_type('/tmp/song.mp3')
|
305
|
+
assert_equal 'text/css', o.file_type('/tmp/main.css')
|
306
|
+
assert_equal nil, o.file_type('unknown.yyy')
|
307
|
+
|
308
|
+
# test using file object
|
309
|
+
img_type = open(sd('ruby.png')) {|f| o.file_type(f)}
|
310
|
+
assert_equal 'image/png', img_type
|
311
|
+
refute_equal 'image/jpeg', img_type
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_object_instantiation_using_discrete_media_factory
|
315
|
+
app_file = sd('book.pdf')
|
316
|
+
audio_file = sd('song.mp3')
|
317
|
+
text_file = sd('data.xml')
|
318
|
+
video_file = sd('mini.mov')
|
319
|
+
image_file = sd('image.jpg')
|
320
|
+
unknown_file = sd('unknown.yyy')
|
321
|
+
|
322
|
+
dmf = MIME::DiscreteMediaFactory
|
323
|
+
|
324
|
+
# test using file path
|
325
|
+
assert_kind_of MIME::ApplicationMedia, dmf.create(app_file)
|
326
|
+
assert_kind_of MIME::AudioMedia, dmf.create(audio_file)
|
327
|
+
assert_kind_of MIME::TextMedia, dmf.create(text_file)
|
328
|
+
assert_kind_of MIME::VideoMedia, dmf.create(video_file)
|
329
|
+
assert_kind_of MIME::ImageMedia, dmf.create(image_file)
|
330
|
+
|
331
|
+
# test using file object
|
332
|
+
open(image_file) do |image_file_obj|
|
333
|
+
assert_kind_of MIME::ImageMedia, dmf.create(image_file_obj)
|
334
|
+
end
|
335
|
+
open(text_file) do |text_file_obj|
|
336
|
+
assert_kind_of MIME::TextMedia, dmf.create(text_file_obj)
|
337
|
+
end
|
338
|
+
|
339
|
+
# raise for unknown file path and File object
|
340
|
+
assert_raises(MIME::UnknownContentError) {dmf.create(unknown_file)}
|
341
|
+
open(unknown_file) do |unknown_file_obj|
|
342
|
+
assert_raises(MIME::UnknownContentError) {dmf.create(unknown_file_obj)}
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def test_discrete_media_factory_creates_path_singleton_method
|
347
|
+
pdf_file_path = sd('book.pdf')
|
348
|
+
|
349
|
+
media_obj = MIME::DiscreteMediaFactory.create(pdf_file_path)
|
350
|
+
assert_equal pdf_file_path, media_obj.path
|
351
|
+
|
352
|
+
open(pdf_file_path) do |pdf_file_obj|
|
353
|
+
media_obj = MIME::DiscreteMediaFactory.create(pdf_file_obj)
|
354
|
+
assert_equal pdf_file_path, media_obj.path
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# rm multipart_alternative.msg
|
359
|
+
def test_multipart_alternative_message_construction
|
360
|
+
txt_data = "*Header*\nmessage"
|
361
|
+
htm_data = "<html><body><h1>Header</h1><p>message</p></body></html>"
|
362
|
+
txt_msg = MIME::TextMedia.new(txt_data)
|
363
|
+
htm_msg = MIME::TextMedia.new(htm_data)
|
364
|
+
|
365
|
+
txt_msg.content_type = (txt_type = 'text/enhanced; charset=us-ascii')
|
366
|
+
htm_msg.content_type = (htm_type = 'text/html; charset=iso-8859-1')
|
367
|
+
|
368
|
+
alt_msg = MIME::MultipartMedia::Alternative.new
|
369
|
+
alt_msg.add_entity htm_msg
|
370
|
+
alt_msg.add_entity txt_msg
|
371
|
+
msg = alt_msg.to_s
|
372
|
+
|
373
|
+
parts = msg.split(BOUNDARY)
|
374
|
+
assert_equal 4, parts.size, 'main header, 2 entities, last boundary'
|
375
|
+
assert_equal '--', parts.pop, 'remnant of last boundary marker'
|
376
|
+
|
377
|
+
# main header
|
378
|
+
assert_match CONTENT_ID, parts[0]
|
379
|
+
assert_match CTYPE_MPART_ALT, parts[0]
|
380
|
+
assert_equal_num_headers 2, parts[0]
|
381
|
+
assert_equal_body '', parts[0]
|
382
|
+
|
383
|
+
# text entity
|
384
|
+
assert_match CONTENT_ID, parts[1]
|
385
|
+
assert_match /^Content-Type: #{txt_type}\r\n/, parts[1]
|
386
|
+
assert_equal_num_headers 2, parts[1]
|
387
|
+
assert_equal_body txt_data, parts[1]
|
388
|
+
|
389
|
+
# html entity
|
390
|
+
assert_match CONTENT_ID, parts[2]
|
391
|
+
assert_match /^Content-Type: #{htm_type}\r\n/, parts[2]
|
392
|
+
assert_equal_num_headers 2, parts[2]
|
393
|
+
assert_equal_body htm_data, parts[2]
|
394
|
+
end
|
395
|
+
|
396
|
+
# rm multipart_mixed_inline_and_attachment.msg
|
397
|
+
def test_multipart_mixed_with_inline_and_attachment
|
398
|
+
mixed_msg = MIME::MultipartMedia::Mixed.new
|
399
|
+
|
400
|
+
img_filename = 'image.jpg'
|
401
|
+
img_data = ''
|
402
|
+
open(sd(img_filename)) do |img_file|
|
403
|
+
img_data = img_file.read
|
404
|
+
img_msg = MIME::ImageMedia.new(img_data, 'image/jpeg')
|
405
|
+
mixed_msg.attach_entity(img_msg, 'filename' => img_file.path)
|
406
|
+
end
|
407
|
+
|
408
|
+
txt_data = 'This is plain text.'
|
409
|
+
mixed_msg.inline_entity(MIME::TextMedia.new(txt_data))
|
410
|
+
msg = mixed_msg.to_s
|
411
|
+
|
412
|
+
parts = msg.split(BOUNDARY)
|
413
|
+
assert_equal 4, parts.size, 'main header, 2 entities, last boundary'
|
414
|
+
assert_equal '--', parts.pop, 'remnant of last boundary marker'
|
415
|
+
|
416
|
+
# main header
|
417
|
+
assert_match CONTENT_ID, parts[0]
|
418
|
+
assert_match CTYPE_MPART_MIXED, parts[0]
|
419
|
+
assert_equal_num_headers 2, parts[0]
|
420
|
+
assert_equal_body '', parts[0]
|
421
|
+
|
422
|
+
# text entity
|
423
|
+
assert_match CONTENT_ID, parts[1]
|
424
|
+
assert_match CTYPE_TEXT_PLAIN, parts[1]
|
425
|
+
assert_match /^Content-Disposition: inline\r\n/, parts[1]
|
426
|
+
assert_equal_num_headers 3, parts[1]
|
427
|
+
assert_equal_body txt_data, parts[1]
|
428
|
+
|
429
|
+
# image entity
|
430
|
+
assert_match CONTENT_ID, parts[2]
|
431
|
+
assert_match CTYPE_IMAGE_JPEG, parts[2]
|
432
|
+
assert_match /^Content-Disposition: attachment; filename="#{img_filename}"\r\n/, parts[2]
|
433
|
+
assert_equal_num_headers 3, parts[2]
|
434
|
+
assert_equal_body img_data, parts[2]
|
435
|
+
end
|
436
|
+
|
437
|
+
# rm multipart_mixed_inline_and_attachment2.msg
|
438
|
+
def test_multipart_mixed_message_construction_using_media_factory
|
439
|
+
img1 = sd(img1_filename = 'image.jpg')
|
440
|
+
img2 = sd(img2_filename = 'ruby.png')
|
441
|
+
txt = sd(txt_filename = 'data.htm')
|
442
|
+
bot_img = MIME::DiscreteMediaFactory.create(img1)
|
443
|
+
top_img = MIME::DiscreteMediaFactory.create(img2)
|
444
|
+
top_txt = MIME::DiscreteMediaFactory.create(txt)
|
445
|
+
|
446
|
+
mixed_msg = MIME::MultipartMedia::Mixed.new
|
447
|
+
mixed_msg.attach_entity(bot_img)
|
448
|
+
mixed_msg.attach_entity(top_img)
|
449
|
+
mixed_msg.inline_entity(top_txt)
|
450
|
+
msg = mixed_msg.to_s
|
451
|
+
|
452
|
+
parts = msg.split(BOUNDARY)
|
453
|
+
assert_equal 5, parts.size, 'main header, 2 entities, last boundary'
|
454
|
+
assert_equal '--', parts.pop, 'remnant of last boundary marker'
|
455
|
+
|
456
|
+
# main header
|
457
|
+
assert_match CONTENT_ID, parts[0]
|
458
|
+
assert_match CTYPE_MPART_MIXED, parts[0]
|
459
|
+
assert_equal_num_headers 2, parts[0]
|
460
|
+
assert_equal_body '', parts[0]
|
461
|
+
|
462
|
+
# html entity
|
463
|
+
assert_match CONTENT_ID, parts[1]
|
464
|
+
assert_match CTYPE_TEXT_HTML, parts[1]
|
465
|
+
assert_match /^Content-Disposition: inline; filename="#{txt_filename}"\r\n/, parts[1]
|
466
|
+
assert_equal_num_headers 3, parts[1]
|
467
|
+
assert_equal_body top_txt.send(:body), parts[1]
|
468
|
+
|
469
|
+
# png image entity
|
470
|
+
assert_match CONTENT_ID, parts[2]
|
471
|
+
assert_match CTYPE_IMAGE_PNG, parts[2]
|
472
|
+
assert_match /^Content-Disposition: attachment; filename="#{img2_filename}"\r\n/, parts[2]
|
473
|
+
assert_equal_num_headers 3, parts[2]
|
474
|
+
assert_equal_body top_img.send(:body), parts[2]
|
475
|
+
|
476
|
+
# jpg image entity
|
477
|
+
assert_match CONTENT_ID, parts[3]
|
478
|
+
assert_match CTYPE_IMAGE_JPEG, parts[3]
|
479
|
+
assert_match /^Content-Disposition: attachment; filename="#{img1_filename}"\r\n/, parts[3]
|
480
|
+
assert_equal_num_headers 3, parts[3]
|
481
|
+
assert_equal_body bot_img.send(:body), parts[3]
|
482
|
+
end
|
483
|
+
|
484
|
+
def test_multipart_form_data_with_mixed_entity
|
485
|
+
txt = MIME::TextMedia.new('Joe Blow')
|
486
|
+
img1 = MIME::DiscreteMediaFactory.create(sd('image.jpg'))
|
487
|
+
img2 = MIME::DiscreteMediaFactory.create(sd('ruby.png'))
|
488
|
+
|
489
|
+
mixed_msg = MIME::MultipartMedia::Mixed.new
|
490
|
+
mixed_msg.attach_entity(img2)
|
491
|
+
mixed_msg.attach_entity(img1)
|
492
|
+
|
493
|
+
form = MIME::MultipartMedia::FormData.new
|
494
|
+
form.add_entity(mixed_msg, 'pics')
|
495
|
+
form.add_entity(txt, 'field1')
|
496
|
+
|
497
|
+
# similar to example 6 in RFC1867
|
498
|
+
expected = IO.read(sd('multipart_form_data_mixed.msg'))
|
499
|
+
|
500
|
+
assert_equal_mime_message expected, form.to_s
|
501
|
+
|
502
|
+
#IO.write('/tmp/mime.out', msg)
|
503
|
+
end
|
504
|
+
|
505
|
+
# def test_multipart_related_html_message_with_embedded_image
|
506
|
+
# img = DiscreteMediaFactory.create(sd('/ruby.png'))
|
507
|
+
# img.content_transfer_encoding = 'binary'
|
508
|
+
#
|
509
|
+
# html_msg = TextMedia.new(<<-html, 'text/html; charset=iso-8859-1')
|
510
|
+
# <html>
|
511
|
+
# <body>
|
512
|
+
# <h1>HTML multipart/related message</h1>
|
513
|
+
# <p>txt before pix</p>
|
514
|
+
# <img alt="cool ruby" src="cid:#{img.content_id}"/>
|
515
|
+
# <p>txt after pix</p>
|
516
|
+
# </body>
|
517
|
+
# </html>
|
518
|
+
# html
|
519
|
+
# html_msg.content_transfer_encoding = '7bit'
|
520
|
+
#
|
521
|
+
# related_msg = MultipartMedia::Related.new
|
522
|
+
# related_msg.inline_entity(img)
|
523
|
+
# related_msg.add_entity(html_msg)
|
524
|
+
#
|
525
|
+
# expected = IO.read(sd('/multipart_related.msg'))
|
526
|
+
#
|
527
|
+
# assert_equal_mime_message expected, related_msg.to_s
|
528
|
+
# end
|
529
|
+
#
|
530
|
+
# def test_multipart_alternative_with_related_html_entity
|
531
|
+
# img = DiscreteMediaFactory.create(sd('/ruby.png'))
|
532
|
+
# img.content_transfer_encoding = 'binary'
|
533
|
+
#
|
534
|
+
# html_msg = TextMedia.new(<<-html, 'text/html; charset=iso-8859-1')
|
535
|
+
# <html>
|
536
|
+
# <body>
|
537
|
+
# <h1>HTML multipart/alternative message</h1>
|
538
|
+
# <p>txt before pix</p>
|
539
|
+
# <img alt="cool ruby" src="cid:#{img.content_id}"/>
|
540
|
+
# <p>txt after pix</p>
|
541
|
+
# </body>
|
542
|
+
# </html>
|
543
|
+
# html
|
544
|
+
# html_msg.content_transfer_encoding = '7bit'
|
545
|
+
#
|
546
|
+
# text_msg = TextMedia.new(<<-text)
|
547
|
+
# *HTML multipart/alternative message*
|
548
|
+
# txt before pix
|
549
|
+
# <cool ruby image>
|
550
|
+
# txt after pix
|
551
|
+
# text
|
552
|
+
# text_msg.content_transfer_encoding = '7bit'
|
553
|
+
#
|
554
|
+
# related_msg = MultipartMedia::Related.new
|
555
|
+
# related_msg.inline_entity(img)
|
556
|
+
# related_msg.add_entity(html_msg)
|
557
|
+
#
|
558
|
+
# alt_msg = MultipartMedia::Alternative.new
|
559
|
+
# alt_msg.add_entity(related_msg)
|
560
|
+
# alt_msg.add_entity(text_msg)
|
561
|
+
#
|
562
|
+
# expected = IO.read(sd('/multipart_alternative_related.msg'))
|
563
|
+
#
|
564
|
+
# assert_equal_mime_message expected, alt_msg.to_s
|
565
|
+
# end
|
566
|
+
|
567
|
+
|
568
|
+
private
|
569
|
+
|
570
|
+
#
|
571
|
+
# Test the equality of the normalized +expected+ and +actual+ MIME messages.
|
572
|
+
#
|
573
|
+
def assert_equal_mime_message expected, actual
|
574
|
+
assert_equal normalize_message(expected), normalize_message(actual)
|
575
|
+
end
|
576
|
+
|
577
|
+
#
|
578
|
+
# Make messages comparable by removing *-ID header values, library version
|
579
|
+
# comment in MIME-Version header, Date header value, multipart/related
|
580
|
+
# content IDs, and boundaries.
|
581
|
+
#
|
582
|
+
def normalize_message message
|
583
|
+
# these are very delicate REs that are inter-dependent, be careful
|
584
|
+
match_id_headers = /-ID: <[^>]+>\r\n/
|
585
|
+
match_boundaries = /Boundary_\d+\.\d+/
|
586
|
+
match_lib_version = / \(Ruby MIME v\d\.\d\)/
|
587
|
+
match_date_header = /^Date: .*\d{4}\r\n/
|
588
|
+
match_related_cid = /cid:\d+\.\d+/
|
589
|
+
|
590
|
+
message.
|
591
|
+
gsub(match_related_cid, "cid").
|
592
|
+
gsub(match_id_headers, "-ID:\r\n").
|
593
|
+
gsub(match_boundaries, "Boundary_").
|
594
|
+
sub(match_date_header, "Date:\r\n").
|
595
|
+
sub(match_lib_version, " (Ruby MIME v0.0)")
|
596
|
+
end
|
597
|
+
|
598
|
+
def assert_equal_num_headers num, msg
|
599
|
+
assert_equal num, msg.split(HDR_BDY_SEP).first.split(CRLF).count
|
600
|
+
end
|
601
|
+
|
602
|
+
def assert_equal_body expected_body, msg
|
603
|
+
# FIXME the next line should be a fix
|
604
|
+
headers, actual_body = msg.split(HDR_BDY_SEP)
|
605
|
+
assert_equal expected_body, actual_body.to_s.chomp
|
606
|
+
#assert_equal body, msg.split("\r\n").last
|
607
|
+
end
|
608
|
+
|
609
|
+
#
|
610
|
+
# Return the absolute path of +file+ under the test/scaffold directory.
|
611
|
+
#
|
612
|
+
def sd file
|
613
|
+
File.join(File.dirname(__FILE__), 'scaffold', file)
|
614
|
+
end
|
615
|
+
|
616
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clint Pachl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
A library for building RFC compliant Multipurpose Internet Mail Extensions
|
@@ -61,6 +61,7 @@ files:
|
|
61
61
|
- test/scaffold/unknown.yyy
|
62
62
|
- test/scaffold/video.msg
|
63
63
|
- test/test_mime.rb
|
64
|
+
- test/test_mime.rb-try
|
64
65
|
- test/test_text_flowed.rb
|
65
66
|
homepage: http://ecentryx.com/gems/mime
|
66
67
|
licenses:
|
@@ -82,10 +83,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
83
|
version: '0'
|
83
84
|
requirements: []
|
84
85
|
rubyforge_project:
|
85
|
-
rubygems_version: 2.
|
86
|
+
rubygems_version: 2.4.5
|
86
87
|
signing_key:
|
87
88
|
specification_version: 4
|
88
89
|
summary: Multipurpose Internet Mail Extensions (MIME) Library
|
89
90
|
test_files:
|
90
|
-
- test/test_text_flowed.rb
|
91
91
|
- test/test_mime.rb
|
92
|
+
- test/test_text_flowed.rb
|