mime 0.1 → 0.2.0
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.
- data/README.rdoc +303 -0
- data/Rakefile +8 -24
- data/lib/mime.rb +1 -1
- data/lib/mime/discrete_media_factory.rb +2 -3
- data/lib/mime/headers/internet.rb +15 -13
- data/lib/mime/headers/mime.rb +18 -18
- data/lib/mime/message.rb +10 -0
- data/mime.gemspec +18 -0
- data/test/scaffold/application.msg +4 -4
- data/test/scaffold/audio.msg +3 -3
- data/test/scaffold/{index.html → data.htm} +0 -0
- data/test/scaffold/image.msg +0 -0
- data/test/scaffold/multipart_alternative.msg +10 -12
- data/test/scaffold/multipart_alternative_related.msg +0 -0
- data/test/scaffold/multipart_form_data_file_and_text.msg +0 -0
- data/test/scaffold/multipart_form_data_mixed.msg +0 -0
- data/test/scaffold/multipart_form_data_text.msg +31 -26
- data/test/scaffold/multipart_mixed_inline_and_attachment.msg +0 -0
- data/test/scaffold/multipart_mixed_inline_and_attachment2.msg +0 -0
- data/test/scaffold/multipart_related.msg +0 -0
- data/test/scaffold/plain_text_email.msg +9 -7
- data/test/scaffold/text.msg +2 -2
- data/test/scaffold/video.msg +4 -4
- data/test/test_mime.rb +417 -0
- data/test/test_mime.rb-try +616 -0
- metadata +53 -51
- data/README +0 -256
- data/test/mime_test.rb +0 -386
- data/test/scaffold/multipart_form_data_file.msg +0 -0
@@ -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
|