mime 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/README.rdoc +192 -140
  2. data/lib/mime/composite_media.rb +230 -0
  3. data/lib/mime/content_formats/text_flowed.rb +6 -6
  4. data/lib/mime/discrete_media.rb +73 -0
  5. data/lib/mime/discrete_media_factory.rb +19 -13
  6. data/lib/mime/header.rb +48 -0
  7. data/lib/mime/headers/internet.rb +124 -27
  8. data/lib/mime/headers/mime.rb +38 -27
  9. data/lib/mime/mail.rb +51 -0
  10. data/lib/mime/media.rb +30 -0
  11. data/lib/mime/parser.rb +1 -1
  12. data/lib/mime.rb +27 -9
  13. data/test/scaffold/application.msg +2 -5
  14. data/test/scaffold/audio.msg +2 -5
  15. data/test/scaffold/image.msg +0 -0
  16. data/test/scaffold/multipart_alternative.msg +7 -7
  17. data/test/scaffold/multipart_alternative_related.msg +0 -0
  18. data/test/scaffold/multipart_form_data_file_and_text.msg +0 -0
  19. data/test/scaffold/multipart_form_data_mixed.msg +0 -0
  20. data/test/scaffold/multipart_form_data_text.msg +12 -12
  21. data/test/scaffold/multipart_mixed_inline_and_attachment.msg +0 -0
  22. data/test/scaffold/multipart_mixed_inline_and_attachment2.msg +0 -0
  23. data/test/scaffold/multipart_related.msg +0 -0
  24. data/test/scaffold/rfc822_composite.msg +0 -0
  25. data/test/scaffold/{plain_text_email.msg → rfc822_discrete.msg} +5 -5
  26. data/test/scaffold/text.msg +2 -5
  27. data/test/scaffold/video.msg +2 -5
  28. data/test/test_mime.rb +323 -150
  29. data/test/test_text_flowed.rb +1 -1
  30. metadata +13 -12
  31. data/lib/mime/composite_media_type.rb +0 -169
  32. data/lib/mime/discrete_media_type.rb +0 -78
  33. data/lib/mime/header_container.rb +0 -32
  34. data/lib/mime/media_type.rb +0 -51
  35. data/lib/mime/message.rb +0 -61
data/test/test_mime.rb CHANGED
@@ -10,63 +10,108 @@ class MIMETest < Minitest::Test
10
10
  CRLF = "\r\n"
11
11
  BINARY_DATA = '0110000101110101011001000110100101101111'
12
12
 
13
+ ID_SPEC = '\w+@[\w.-]+'
14
+ DATE_SPEC = '..., \d{1,2} ... \d{4} \d\d:\d\d:\d\d -\d{4}'
15
+ VERSION_SPEC = '\(Ruby MIME v\d\.\d\.\d\)'
16
+ BOUNDARY_SPEC = 'Boundary_\w+'
13
17
 
14
- ### DISCRETE MESSAGE CONSTRUCTION (SINGLE ENTITY) ###
15
18
 
16
- def test_make_top_level_rfc2822_message
17
- msg = MIME::Message.new
18
- msg.body = "\r\nmessage body"
19
+ ### RFC 822 MESSAGE CONSTRUCTION ###
20
+
21
+ def test_rfc822_message
22
+ msg = MIME::Mail.new('message body')
19
23
  headers, body = msg.to_s.split(CRLF*2)
20
24
 
21
- assert_match(/^Message-ID: <\d+@\d+>/, headers)
22
- assert_match(/^Date: ..., \d{1,2} ... \d{4} \d\d:\d\d:\d\d -\d{4}/, headers)
23
- assert_match(/^MIME-Version: 1.0 \(Ruby MIME v\d\.\d\.\d\)/, headers)
24
- assert_equal 3, headers.split(CRLF).count
25
- assert_equal "message body", body.chomp
25
+ assert_match(/^Message-ID: <#{ID_SPEC}>/, headers)
26
+ assert_match(/^Date: #{DATE_SPEC}/, headers)
27
+ assert_match(/^MIME-Version: 1.0 #{VERSION_SPEC}/, headers)
28
+ assert_match(/^Content-ID: <#{ID_SPEC}>/, headers)
29
+ assert_match(/^Content-Type: text\/plain/, headers)
30
+ assert_equal 5, headers.split(CRLF).count
31
+ assert_equal 'message body', body
26
32
  end
27
33
 
28
- def test_make_audio_message
29
- audio_media = MIME::AudioMedia.new(BINARY_DATA, 'midi')
30
- audio_media.content_transfer_encoding = 'binary'
31
- assert_equal_mime_msg 'audio.msg', MIME::Message.new(audio_media)
34
+ def test_rfc822_message_with_empty_body
35
+ assert_match(/#{VERSION_SPEC}#{CRLF*2}$/, MIME::Mail.new.to_s)
32
36
  end
33
37
 
34
- def test_make_application_message
35
- app_media = MIME::ApplicationMedia.new(BINARY_DATA)
36
- app_media.content_transfer_encoding = 'binary'
37
- assert_equal_mime_msg 'application.msg', MIME::Message.new(app_media)
38
+ def test_rfc822_message_with_discrete_media_body
39
+ email = MIME::Mail.new
40
+ email.to = {
41
+ 'john@example.com' => 'John',
42
+ 'paul@example.com' => nil,
43
+ 'mary@example.com' => 'Mary'
44
+ }
45
+ email.cc = {'boss@example.com' => 'Head Honcho'}
46
+ email.from = 'jane@example.com'
47
+ email.subject = 'This is an important email'
48
+ email.body = MIME::Text.new('Hello, world!')
49
+ assert_equal_mime_msg 'rfc822_discrete', email
38
50
  end
39
51
 
40
- def test_make_image_message
41
- image = IO.read(sd('image.jpg'))
42
- image_media = MIME::ImageMedia.new(image)
43
- image_media.content_transfer_encoding = 'binary'
44
- assert_equal_mime_msg 'image.msg', MIME::Message.new(image_media)
52
+ def test_rfc822_message_with_composite_media_body
53
+ img = MIME::DiscreteMediaFactory.create(sd('ruby.png'))
54
+ img.transfer_encoding = 'binary'
55
+
56
+ htm_msg = MIME::Text.new(<<EOF, 'html', 'charset' => 'iso-8859-1')
57
+ <html>
58
+ <body><img alt="cool ruby" src="cid:#{img.id}"></body>
59
+ </html>
60
+ EOF
61
+ related_htm_msg = MIME::Multipart::Related.new
62
+ related_htm_msg.add(htm_msg)
63
+ related_htm_msg.inline(img)
64
+
65
+ txt_msg = MIME::Text.new(<<EOF, 'plain', 'charset' => 'us-ascii')
66
+ Check the attachment for a cool ruby picture.
67
+ EOF
68
+ mixed_txt_msg = MIME::Multipart::Mixed.new
69
+ mixed_txt_msg.add(txt_msg)
70
+ mixed_txt_msg.attach(img)
71
+
72
+ alt_msg = MIME::Multipart::Alternative.new
73
+ alt_msg.add(mixed_txt_msg)
74
+ alt_msg.add(related_htm_msg)
75
+
76
+ email = MIME::Mail.new(alt_msg)
77
+ email.from = 'john@example.com'
78
+ email.to = 'jane@example.com'
79
+ email.subject = 'Cool ruby pic'
80
+
81
+ assert_equal_mime_msg 'rfc822_composite', email
45
82
  end
46
83
 
47
- def test_make_text_message
48
- body = MIME::TextMedia.new('a plain text message')
49
- assert_equal_mime_msg 'text.msg', MIME::Message.new(body)
84
+
85
+ ### DISCRETE MESSAGE CONSTRUCTION (SINGLE ENTITY) ###
86
+
87
+ def test_audio_message
88
+ audio_media = MIME::Audio.new(BINARY_DATA, 'midi')
89
+ audio_media.transfer_encoding = 'binary'
90
+ assert_equal_mime_msg 'audio', audio_media
50
91
  end
51
92
 
52
- def test_make_video_message
53
- video_media = MIME::VideoMedia.new(BINARY_DATA)
54
- video_media.content_transfer_encoding = 'binary'
55
- assert_equal_mime_msg 'video.msg', MIME::Message.new(video_media)
93
+ def test_application_message
94
+ app_media = MIME::Application.new(BINARY_DATA)
95
+ app_media.transfer_encoding = 'binary'
96
+ assert_equal_mime_msg 'application', app_media
56
97
  end
57
98
 
58
- def test_construction_of_plain_text_email_message
59
- email = MIME::Message.new
60
- email.to = {
61
- 'john@example.com' => 'John',
62
- 'paul@example.com' => nil,
63
- 'mary@example.com' => 'Mary'
64
- }
65
- email.cc = {'Head Honcho' => 'boss@example.com'}
66
- email.from = {'jane@example.com' => nil}
67
- email.subject = 'This is an important email'
68
- email.body = MIME::TextMedia.new('Hello, world!')
69
- assert_equal_mime_msg 'plain_text_email.msg', email
99
+ def test_image_message
100
+ image = IO.read(sd('image.jpg'))
101
+ image_media = MIME::Image.new(image)
102
+ image_media.transfer_encoding = 'binary'
103
+ assert_equal_mime_msg 'image', image_media
104
+ end
105
+
106
+ def test_text_message
107
+ text_media = MIME::Text.new('a plain text message')
108
+ assert_equal_mime_msg 'text', text_media
109
+ end
110
+
111
+ def test_video_message
112
+ video_media = MIME::Video.new(BINARY_DATA)
113
+ video_media.transfer_encoding = 'binary'
114
+ assert_equal_mime_msg 'video', video_media
70
115
  end
71
116
 
72
117
 
@@ -77,15 +122,15 @@ class MIMETest < Minitest::Test
77
122
  htm_data = IO.read(sd('data.htm'))
78
123
  xml_data = IO.read(sd('data.xml'))
79
124
 
80
- txt = MIME::TextMedia.new(txt_data)
81
- htm = MIME::TextMedia.new(htm_data, 'html')
82
- xml = MIME::TextMedia.new(xml_data, 'xml')
125
+ txt = MIME::Text.new(txt_data)
126
+ htm = MIME::Text.new(htm_data, 'html')
127
+ xml = MIME::Text.new(xml_data, 'xml')
83
128
 
84
- form = MIME::MultipartMedia::FormData.new
85
- form.add_entity txt, 'txt'
86
- form.add_entity htm, 'htm'
87
- form.add_entity xml, 'xml'
88
- assert_equal_mime_msg 'multipart_form_data_text.msg', form
129
+ form = MIME::Multipart::FormData.new
130
+ form.add xml, 'xml'
131
+ form.add htm, 'htm'
132
+ form.add txt, 'txt'
133
+ assert_equal_mime_msg 'multipart_form_data_text', form
89
134
  end
90
135
 
91
136
  def test_multipart_form_data_with_text_and_file_entities
@@ -93,95 +138,95 @@ class MIMETest < Minitest::Test
93
138
  img2_filename = 'ruby.png'
94
139
  img1_data = IO.read(sd(img1_filename))
95
140
  img2_data = IO.read(sd(img2_filename))
96
- img1 = MIME::ImageMedia.new(img1_data, 'jpeg')
97
- img2 = MIME::ImageMedia.new(img2_data, 'png')
98
- img1.content_transfer_encoding = '8bit'
99
- img2.content_transfer_encoding = '8bit'
141
+ img1 = MIME::Image.new(img1_data, 'jpeg')
142
+ img2 = MIME::Image.new(img2_data, 'png')
143
+ img1.transfer_encoding = '8bit'
144
+ img2.transfer_encoding = '8bit'
100
145
 
101
146
  desc_data = 'This is plain text description of images.'
102
- desc = MIME::TextMedia.new(desc_data, 'plain', 'charset' => 'us-ascii')
147
+ desc = MIME::Text.new(desc_data, 'plain', 'charset' => 'us-ascii')
103
148
 
104
- form = MIME::MultipartMedia::FormData.new
105
- form.add_entity desc, 'description'
106
- form.add_entity img2, 'image_2', img2_filename
107
- form.add_entity img1, 'image_1', img1_filename
108
- assert_equal_mime_msg 'multipart_form_data_file_and_text.msg', form
149
+ form = MIME::Multipart::FormData.new
150
+ form.add img1, 'image_1', img1_filename
151
+ form.add img2, 'image_2', img2_filename
152
+ form.add desc, 'description'
153
+ assert_equal_mime_msg 'multipart_form_data_file_and_text', form
109
154
  end
110
155
 
111
156
  # Similar to example 6 in RFC1867.
112
157
  def test_multipart_form_data_with_mixed_entity
113
- txt = MIME::TextMedia.new('Joe Blow')
158
+ txt = MIME::Text.new('Joe Blow')
114
159
  img1 = MIME::DiscreteMediaFactory.create(sd('image.jpg'))
115
160
  img2 = MIME::DiscreteMediaFactory.create(sd('ruby.png'))
116
161
 
117
- mixed_msg = MIME::MultipartMedia::Mixed.new
118
- mixed_msg.attach_entity(img2)
119
- mixed_msg.attach_entity(img1)
162
+ mixed_msg = MIME::Multipart::Mixed.new
163
+ mixed_msg.attach(img1)
164
+ mixed_msg.attach(img2)
120
165
 
121
- form = MIME::MultipartMedia::FormData.new
122
- form.add_entity(mixed_msg, 'pics')
123
- form.add_entity(txt, 'field1')
166
+ form = MIME::Multipart::FormData.new
167
+ form.add(txt, 'field1')
168
+ form.add(mixed_msg, 'pics')
124
169
 
125
- assert_equal_mime_msg 'multipart_form_data_mixed.msg', form
170
+ assert_equal_mime_msg 'multipart_form_data_mixed', form
126
171
  end
127
172
 
128
173
  def test_multipart_alternative_message
129
174
  txt_data = "*Header*\nmessage"
130
175
  htm_data = "<html><body><h1>Header</h1><p>message</p></body></html>"
131
- txt_msg = MIME::TextMedia.new(txt_data, 'enhanced', 'charset' => 'us-ascii')
132
- htm_msg = MIME::TextMedia.new(htm_data, 'html', 'charset' => 'iso-8859-1')
176
+ txt_msg = MIME::Text.new(txt_data, 'enhanced', 'charset' => 'us-ascii')
177
+ htm_msg = MIME::Text.new(htm_data, 'html', 'charset' => 'iso-8859-1')
133
178
 
134
- msg = MIME::MultipartMedia::Alternative.new
135
- msg.add_entity htm_msg
136
- msg.add_entity txt_msg
137
- assert_equal_mime_msg 'multipart_alternative.msg', msg
179
+ msg = MIME::Multipart::Alternative.new
180
+ msg.add txt_msg
181
+ msg.add htm_msg
182
+ assert_equal_mime_msg 'multipart_alternative', msg
138
183
  end
139
184
 
140
185
  def test_multipart_alternative_with_related_html_entity
141
186
  img = MIME::DiscreteMediaFactory.create(sd('ruby.png'))
142
- img.content_transfer_encoding = 'binary'
187
+ img.transfer_encoding = 'binary'
143
188
 
144
- html_msg = MIME::TextMedia.new(<<EOF, 'html', 'charset' => 'iso-8859-1')
189
+ html_msg = MIME::Text.new(<<EOF, 'html', 'charset' => 'iso-8859-1')
145
190
  <html>
146
191
  <body>
147
192
  <h1>HTML multipart/alternative message</h1>
148
193
  <p>txt before pix</p>
149
- <img alt="cool ruby" src="cid:#{img.content_id}"/>
194
+ <img alt="cool ruby" src="cid:#{img.id}"/>
150
195
  <p>txt after pix</p>
151
196
  </body>
152
197
  </html>
153
198
  EOF
154
- html_msg.content_transfer_encoding = '7bit'
199
+ html_msg.transfer_encoding = '7bit'
155
200
 
156
- text_msg = MIME::TextMedia.new(<<EOF, 'plain', 'charset' => 'us-ascii')
201
+ text_msg = MIME::Text.new(<<EOF, 'plain', 'charset' => 'us-ascii')
157
202
  *HTML multipart/alternative message*
158
203
  txt before pix
159
204
  <cool ruby image>
160
205
  txt after pix
161
206
  EOF
162
- text_msg.content_transfer_encoding = '7bit'
207
+ text_msg.transfer_encoding = '7bit'
163
208
 
164
- related_msg = MIME::MultipartMedia::Related.new
165
- related_msg.inline_entity(img)
166
- related_msg.add_entity(html_msg)
209
+ related_msg = MIME::Multipart::Related.new
210
+ related_msg.add(html_msg)
211
+ related_msg.inline(img)
167
212
 
168
- msg = MIME::MultipartMedia::Alternative.new
169
- msg.add_entity(related_msg)
170
- msg.add_entity(text_msg)
171
- assert_equal_mime_msg 'multipart_alternative_related.msg', msg
213
+ msg = MIME::Multipart::Alternative.new
214
+ msg.add(text_msg)
215
+ msg.add(related_msg)
216
+ assert_equal_mime_msg 'multipart_alternative_related', msg
172
217
  end
173
218
 
174
219
  def test_multipart_mixed_with_inline_and_attachment
175
- msg = MIME::MultipartMedia::Mixed.new
220
+ msg = MIME::Multipart::Mixed.new
221
+ msg.inline(MIME::Text.new('Plain Text'))
176
222
 
177
223
  open(sd('image.jpg')) do |img_file|
178
224
  img_data = img_file.read
179
- img_msg = MIME::ImageMedia.new(img_data, 'jpeg')
180
- msg.attach_entity(img_msg, 'filename' => img_file.path)
225
+ img_msg = MIME::Image.new(img_data, 'jpeg')
226
+ msg.attach(img_msg, 'filename' => img_file.path)
181
227
  end
182
228
 
183
- msg.inline_entity(MIME::TextMedia.new('Plain Text'))
184
- assert_equal_mime_msg 'multipart_mixed_inline_and_attachment.msg', msg
229
+ assert_equal_mime_msg 'multipart_mixed_inline_and_attachment', msg
185
230
  end
186
231
 
187
232
  def test_multipart_mixed_message_using_media_factory
@@ -189,44 +234,44 @@ EOF
189
234
  top_img = MIME::DiscreteMediaFactory.create(sd('ruby.png'))
190
235
  top_txt = MIME::DiscreteMediaFactory.create(sd('data.htm'))
191
236
 
192
- msg = MIME::MultipartMedia::Mixed.new
193
- msg.attach_entity(bot_img)
194
- msg.attach_entity(top_img)
195
- msg.inline_entity(top_txt)
196
- assert_equal_mime_msg 'multipart_mixed_inline_and_attachment2.msg', msg
237
+ msg = MIME::Multipart::Mixed.new
238
+ msg.inline(top_txt)
239
+ msg.attach(top_img)
240
+ msg.attach(bot_img)
241
+ assert_equal_mime_msg 'multipart_mixed_inline_and_attachment2', msg
197
242
  end
198
243
 
199
244
  def test_multipart_related_html_message_with_embedded_image
200
245
  img = MIME::DiscreteMediaFactory.create(sd('/ruby.png'))
201
- img.content_transfer_encoding = 'binary'
246
+ img.transfer_encoding = 'binary'
202
247
 
203
- html_msg = MIME::TextMedia.new(<<EOF, 'html; charset=iso-8859-1')
248
+ html_msg = MIME::Text.new(<<EOF, 'html; charset=iso-8859-1')
204
249
  <html>
205
250
  <body>
206
251
  <h1>HTML multipart/related message</h1>
207
252
  <p>
208
253
  txt before pix
209
- <img alt="cool ruby" src="cid:#{img.content_id}">
254
+ <img alt="cool ruby" src="cid:#{img.id}">
210
255
  </p>
211
256
  <p>txt after pix</p>
212
257
  </body>
213
258
  </html>
214
259
  EOF
215
- html_msg.content_transfer_encoding = '7bit'
260
+ html_msg.transfer_encoding = '7bit'
216
261
 
217
- msg = MIME::MultipartMedia::Related.new
218
- msg.inline_entity(img)
219
- msg.add_entity(html_msg)
220
- assert_equal_mime_msg 'multipart_related.msg', msg
262
+ msg = MIME::Multipart::Related.new
263
+ msg.add(html_msg)
264
+ msg.inline(img)
265
+ assert_equal_mime_msg 'multipart_related', msg
221
266
  end
222
267
 
223
268
 
224
269
  ### GENERAL RFC ADHERENCE ###
225
270
 
226
271
  def test_boundary_format
227
- form = MIME::MultipartMedia::FormData.new
272
+ form = MIME::Multipart::FormData.new
228
273
  %w(one two three four).each do |ent|
229
- form.add_entity(MIME::TextMedia.new(ent), ent)
274
+ form.add(MIME::Text.new(ent), ent)
230
275
  end
231
276
 
232
277
  boundary = form.to_s.scan(/--Boundary_.*\r\n/).each
@@ -235,18 +280,46 @@ EOF
235
280
  assert_equal first_boundary, boundary.next
236
281
  assert_equal first_boundary, boundary.next
237
282
  refute_equal first_boundary, (last_boundary = boundary.next)
238
- assert_match(/^--Boundary_\d+\.\d+\r\n/, first_boundary)
239
- assert_match(/^--Boundary_\d+\.\d+--\r\n/, last_boundary)
283
+ assert_match(/^--#{BOUNDARY_SPEC}\r\n/, first_boundary)
284
+ assert_match(/^--#{BOUNDARY_SPEC}--\r\n/, last_boundary)
285
+ end
286
+
287
+ def test_message_id_auto_generation
288
+ msg = MIME::Mail.new
289
+ assert_nil(msg.message_id)
290
+ msg.to_s
291
+ assert_match(/^#{ID_SPEC}$/, msg.message_id)
292
+ end
293
+
294
+ def test_message_id_manual_assignment
295
+ msg_id = 'id_1234@example.com'
296
+ msg = MIME::Mail.new
297
+ assert_nil(msg.message_id)
298
+ msg.message_id = msg_id
299
+ assert_equal("#{msg_id}", msg.message_id)
300
+ msg.to_s # should not affect message ID
301
+ assert_equal("#{msg_id}", msg.message_id)
302
+ end
303
+
304
+ def test_rfc2822_date_format
305
+ msg = MIME::Mail.new
306
+ assert_kind_of(Time, msg.date)
307
+ assert_match(/^Date: #{msg.date.rfc2822}\r\n/, msg.to_s)
308
+ hour_ago = Time.now() - 3600
309
+ msg.date = hour_ago
310
+ assert_match(/^Date: #{hour_ago.rfc2822}\r\n/, msg.to_s)
311
+ msg.date += 3600
312
+ refute_match(/^Date: #{hour_ago.rfc2822}\r\n/, msg.to_s)
240
313
  end
241
314
 
242
315
  def test_unique_content_ids_in_multipart_message
243
- form = MIME::MultipartMedia::FormData.new
316
+ form = MIME::Multipart::FormData.new
244
317
  %w(one two three four).each do |ent|
245
- form.add_entity(MIME::TextMedia.new(ent), ent)
318
+ form.add(MIME::Text.new(ent), ent)
246
319
  end
247
320
 
248
321
  # 5 IDs: main header ID + 4 entity IDs
249
- content_ids = form.to_s.scan(/^Content-ID: <(\d+.\d+)>/)
322
+ content_ids = form.to_s.scan(/^Content-ID: <(#{ID_SPEC})>/)
250
323
  assert_equal 5, content_ids.flatten.uniq.count # IDs must be unique
251
324
  end
252
325
 
@@ -259,29 +332,116 @@ EOF
259
332
  file1 = MIME::DiscreteMediaFactory.create(sd filename1)
260
333
  file2 = MIME::DiscreteMediaFactory.create(sd filename2)
261
334
  file3 = MIME::DiscreteMediaFactory.create(sd filename3)
262
- file4 = MIME::TextMedia.new('txt')
263
- file5 = MIME::TextMedia.new('htm')
264
- file6 = MIME::TextMedia.new('xml')
335
+ file4 = MIME::Text.new('txt')
336
+ file5 = MIME::Text.new('htm')
337
+ file6 = MIME::Text.new('xml')
265
338
 
266
- form = MIME::MultipartMedia::FormData.new
339
+ form = MIME::Multipart::FormData.new
267
340
  # file backed objects
268
- form.add_entity file1, 'file_1' # none
269
- form.add_entity file2, 'file_2', filename2 # relative
270
- form.add_entity file3, 'file_3', "/tmp/#{filename3}" # absolute
341
+ form.add file1, 'file_1' # none
342
+ form.add file2, 'file_2', filename2 # relative
343
+ form.add file3, 'file_3', "/tmp/#{filename3}" # absolute
271
344
  # non-file backed objects
272
- form.add_entity file4, 'file_4', "/tmp/#{filename4}" # absolute
273
- form.add_entity file5, 'file_5', filename5 # relative
274
- form.add_entity file6, 'file_6' # none
345
+ form.add file4, 'file_4', "/tmp/#{filename4}" # absolute
346
+ form.add file5, 'file_5', filename5 # relative
347
+ form.add file6, 'file_6' # none
275
348
  msg = form.to_s
276
349
  hdr = 'Content-Disposition: form-data;'
277
350
 
278
351
  # only the file basename should be assigned to filename, never a path
279
- assert_match(/^#{hdr} name="file_1"; filename="#{filename1}"\r\n/, msg)
280
- assert_match(/^#{hdr} name="file_2"; filename="#{filename2}"\r\n/, msg)
281
- assert_match(/^#{hdr} name="file_3"; filename="#{filename3}"\r\n/, msg)
282
- assert_match(/^#{hdr} name="file_4"; filename="#{filename4}"\r\n/, msg)
283
- assert_match(/^#{hdr} name="file_5"; filename="#{filename5}"\r\n/, msg)
284
- assert_match(/^#{hdr} name="file_6"\r\n/, msg)
352
+ assert_match(/^#{hdr} name=file_1; filename=#{filename1}\r\n/, msg)
353
+ assert_match(/^#{hdr} name=file_2; filename=#{filename2}\r\n/, msg)
354
+ assert_match(/^#{hdr} name=file_3; filename=#{filename3}\r\n/, msg)
355
+ assert_match(/^#{hdr} name=file_4; filename=#{filename4}\r\n/, msg)
356
+ assert_match(/^#{hdr} name=file_5; filename=#{filename5}\r\n/, msg)
357
+ assert_match(/^#{hdr} name=file_6\r\n/, msg)
358
+ end
359
+
360
+ # According to RFC 2822, "multiple occurrences of any of the fields" is
361
+ # "obsolete field syntax" and "interpretation of multiple occurrences of
362
+ # fields is unspecified."
363
+ def test_case_insenstive_header_names
364
+ headers = MIME::Header.new
365
+ headers.set 'from', 'user1'
366
+ assert_equal 'from: user1', headers.to_s
367
+ headers.set 'FROM', 'user1'
368
+ assert_equal 'FROM: user1', headers.to_s
369
+ headers.set 'From', 'user2'
370
+ assert_equal 'From: user2', headers.to_s
371
+ headers.delete 'fROM'
372
+ assert_equal '', headers.to_s
373
+ end
374
+
375
+ def test_mailbox_types
376
+ e1 = 'john@example.com'
377
+ e2 = 'jane@example.com'
378
+ to = "#{e1}, #{e2}"
379
+ mb_string = to
380
+ mb_array = [e1, e2]
381
+ mb_hash = {e1 => nil, e2 => nil}
382
+ msg = MIME::Mail.new
383
+ msg.to = mb_string; assert_equal to, msg.headers.get('to')
384
+ msg.to = mb_array; assert_equal to, msg.headers.get('to')
385
+ msg.to = mb_hash; assert_equal to, msg.headers.get('to')
386
+ end
387
+
388
+ def test_mailbox_display_names
389
+ non_alnum = "!$&*-=^`|~#%+/?_{}'"
390
+ email = 'john@example.com'
391
+ msg = MIME::Mail.new
392
+ mailboxes = [
393
+ {email => nil}, # no display name
394
+ {email => %[A #{non_alnum} Z]}, # non-special chars
395
+ {email => %[John]}, # one atom
396
+ {email => %[John Doe]}, # two atoms
397
+ {email => %[John "Dead Man" Doe]}, # special: double quote
398
+ {email => %[John D. Doe]}, # special: period
399
+ {email => %[John Doe, DECEASED]} # special: comma
400
+ ]
401
+ expected = [
402
+ email,
403
+ %[A #{non_alnum} Z <#{email}>],
404
+ %[John <#{email}>],
405
+ %[John Doe <#{email}>],
406
+ %["John \\\"Dead Man\\\" Doe" <#{email}>],
407
+ %["John D. Doe" <#{email}>],
408
+ %["John Doe, DECEASED" <#{email}>]
409
+ ]
410
+ expected.each_with_index do |exp, i|
411
+ msg.to = mailboxes[i]
412
+ assert_equal exp, msg.headers.get('to')
413
+ end
414
+ end
415
+
416
+ def test_sender_field_specification
417
+ e1 = 'john@example.com'
418
+ e2 = 'jane@example.com'
419
+ msg = MIME::Mail.new
420
+
421
+ # sender must contain a single mailbox
422
+ assert_raises(ArgumentError) {msg.sender = [e1, e2]}
423
+ assert_raises(ArgumentError) {msg.sender = {e1=>nil, e2=>nil}}
424
+ msg.sender = [e1]; assert_equal e1, msg.headers.get('sender')
425
+ msg.sender = {e1=>nil}; assert_equal e1, msg.headers.get('sender')
426
+ end
427
+
428
+ # Quoted Strings: https://tools.ietf.org/html/rfc5322#section-3.2.4
429
+ def test_content_disposition_parameter_quoting
430
+ txt = MIME::Text.new('')
431
+ value_test_cases = {
432
+ '123' => '123',
433
+ 'abc' => 'abc',
434
+ 'a.c' => 'a.c',
435
+ '{b}' => '{b}',
436
+ '.bc' => '".bc"', # quote leading period
437
+ '[b]' => '"[b]"', # quote string
438
+ 'a c' => '"a c"', # unless DOT_ATOM
439
+ 'a"c' => '"a\"c"' # escape double quote
440
+ }
441
+ value_test_cases.each do |value, expected|
442
+ txt.__send__(:set_disposition, 'type', 'param' => value)
443
+ assert_equal "type; param=#{expected}", txt.disposition
444
+ end
285
445
  end
286
446
 
287
447
 
@@ -289,10 +449,10 @@ EOF
289
449
 
290
450
  def test_no_instantiation_of_abstract_classes
291
451
  e = MIME::AbstractClassError
292
- assert_raises(e) {MIME::MediaType.new(nil, nil, nil)}
293
- assert_raises(e) {MIME::DiscreteMediaType.new(nil, nil, nil)}
294
- assert_raises(e) {MIME::CompositeMediaType.new(nil)}
295
- assert_raises(e) {MIME::MultipartMedia.new(nil)}
452
+ assert_raises(e) {MIME::Media.new(nil, nil, nil)}
453
+ assert_raises(e) {MIME::DiscreteMedia.new(nil, nil, nil)}
454
+ assert_raises(e) {MIME::CompositeMedia.new(nil)}
455
+ assert_raises(e) {MIME::Multipart.new(nil)}
296
456
  end
297
457
 
298
458
  def test_content_type_detection
@@ -323,18 +483,18 @@ EOF
323
483
  dmf = MIME::DiscreteMediaFactory
324
484
 
325
485
  # test using file path
326
- assert_kind_of MIME::ApplicationMedia, dmf.create(app_file)
327
- assert_kind_of MIME::AudioMedia, dmf.create(audio_file)
328
- assert_kind_of MIME::TextMedia, dmf.create(text_file)
329
- assert_kind_of MIME::VideoMedia, dmf.create(video_file)
330
- assert_kind_of MIME::ImageMedia, dmf.create(image_file)
486
+ assert_kind_of MIME::Application, dmf.create(app_file)
487
+ assert_kind_of MIME::Audio, dmf.create(audio_file)
488
+ assert_kind_of MIME::Text, dmf.create(text_file)
489
+ assert_kind_of MIME::Video, dmf.create(video_file)
490
+ assert_kind_of MIME::Image, dmf.create(image_file)
331
491
 
332
492
  # test using file object
333
493
  open(image_file) do |image_file_obj|
334
- assert_kind_of MIME::ImageMedia, dmf.create(image_file_obj)
494
+ assert_kind_of MIME::Image, dmf.create(image_file_obj)
335
495
  end
336
496
  open(text_file) do |text_file_obj|
337
- assert_kind_of MIME::TextMedia, dmf.create(text_file_obj)
497
+ assert_kind_of MIME::Text, dmf.create(text_file_obj)
338
498
  end
339
499
 
340
500
  # raise for unknown file path and File object
@@ -375,6 +535,20 @@ EOF
375
535
  end
376
536
  end
377
537
 
538
+ def test_content_id
539
+ txt = MIME::Text.new('body') # CID generated on initialize
540
+
541
+ cid1 = txt.id.dup
542
+ assert_match(/^#{ID_SPEC}$/, cid1)
543
+ assert_includes(txt.to_s, cid1) # to_s should not change CID
544
+ assert_equal(cid1, txt.id)
545
+
546
+ txt.id = cid2 = 'id@example.com'
547
+ refute_equal(cid1, txt.id)
548
+ assert_equal(cid2, txt.id)
549
+ assert_includes(txt.to_s, cid2)
550
+ end
551
+
378
552
 
379
553
  private
380
554
 
@@ -382,7 +556,7 @@ EOF
382
556
  # Test equality of the +expected+ and +actual+ MIME messages.
383
557
  #
384
558
  def assert_equal_mime_msg expected, actual
385
- m1 = generalize_msg(IO.read(sd(expected)))
559
+ m1 = generalize_msg(IO.read(sd(expected+'.msg')))
386
560
  m2 = generalize_msg(actual.to_s)
387
561
  assert_equal m1, m2
388
562
  end
@@ -391,19 +565,18 @@ EOF
391
565
  # Remove unique identifiers to make +message+ structurally comparable.
392
566
  #
393
567
  def generalize_msg message
394
- # Very delicate REs that are inter-dependent, be careful!
395
- cid = /cid:\d+\.\d+/
396
- id = /-ID: <\d+[@.]\d+>\r\n/
397
- boundary = /Boundary_\d+\.\d+/
398
- date = /^Date: .* -\d{4}\r\n/
399
- version = /Ruby MIME v\d\.\d\.\d/
568
+ id = /-ID: <#{ID_SPEC}>#{CRLF}/
569
+ cid = /cid:#{ID_SPEC}/
570
+ date = /^Date: #{DATE_SPEC}#{CRLF}/
571
+ version = /#{VERSION_SPEC}#{CRLF}/
572
+ boundary = /#{BOUNDARY_SPEC}/
400
573
 
401
574
  message.
402
- gsub(cid, "cid").
403
- gsub(id, "-ID:\r\n").
404
- gsub(boundary, "Boundary_").
405
- sub(date, "Date:\r\n").
406
- sub(version, "Ruby MIME")
575
+ gsub(id, "-ID: <ID-REMOVED>#{CRLF}").
576
+ gsub(cid, "cid:ID-REMOVED").
577
+ sub(date, "Date: DATE-REMOVED#{CRLF}").
578
+ sub(version, "(Ruby MIME v0.0.0)#{CRLF}").
579
+ gsub(boundary, "Boundary_ID-REMOVED")
407
580
  end
408
581
 
409
582
  #
@@ -112,7 +112,7 @@ class TextFlowedTest < Minitest::Test
112
112
  end
113
113
 
114
114
  def test_rfc_max_smtp_line_length_boundary
115
- long_word = 'x' * MIME::ContentFormats::MAX_SMTP_LINE
115
+ long_word = 'x' * MIME::MAX_LINE_LENGTH
116
116
  txt1 = long_word
117
117
  txt2 = 'x' + long_word
118
118
  txt3 = 'x ' + long_word