mime 0.3.0 → 0.4.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.
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