mime 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +192 -140
- data/lib/mime/composite_media.rb +230 -0
- data/lib/mime/content_formats/text_flowed.rb +6 -6
- data/lib/mime/discrete_media.rb +73 -0
- data/lib/mime/discrete_media_factory.rb +19 -13
- data/lib/mime/header.rb +48 -0
- data/lib/mime/headers/internet.rb +124 -27
- data/lib/mime/headers/mime.rb +38 -27
- data/lib/mime/mail.rb +51 -0
- data/lib/mime/media.rb +30 -0
- data/lib/mime/parser.rb +1 -1
- data/lib/mime.rb +27 -9
- data/test/scaffold/application.msg +2 -5
- data/test/scaffold/audio.msg +2 -5
- data/test/scaffold/image.msg +0 -0
- data/test/scaffold/multipart_alternative.msg +7 -7
- 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 +12 -12
- 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/rfc822_composite.msg +0 -0
- data/test/scaffold/{plain_text_email.msg → rfc822_discrete.msg} +5 -5
- data/test/scaffold/text.msg +2 -5
- data/test/scaffold/video.msg +2 -5
- data/test/test_mime.rb +323 -150
- data/test/test_text_flowed.rb +1 -1
- metadata +13 -12
- data/lib/mime/composite_media_type.rb +0 -169
- data/lib/mime/discrete_media_type.rb +0 -78
- data/lib/mime/header_container.rb +0 -32
- data/lib/mime/media_type.rb +0 -51
- 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
|
-
|
17
|
-
|
18
|
-
|
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:
|
22
|
-
assert_match(/^Date:
|
23
|
-
assert_match(/^MIME-Version: 1.0
|
24
|
-
|
25
|
-
|
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
|
29
|
-
|
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
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
53
|
-
|
54
|
-
|
55
|
-
assert_equal_mime_msg '
|
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
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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::
|
81
|
-
htm = MIME::
|
82
|
-
xml = MIME::
|
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::
|
85
|
-
form.
|
86
|
-
form.
|
87
|
-
form.
|
88
|
-
assert_equal_mime_msg 'multipart_form_data_text
|
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::
|
97
|
-
img2 = MIME::
|
98
|
-
img1.
|
99
|
-
img2.
|
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::
|
147
|
+
desc = MIME::Text.new(desc_data, 'plain', 'charset' => 'us-ascii')
|
103
148
|
|
104
|
-
form = MIME::
|
105
|
-
form.
|
106
|
-
form.
|
107
|
-
form.
|
108
|
-
assert_equal_mime_msg 'multipart_form_data_file_and_text
|
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::
|
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::
|
118
|
-
mixed_msg.
|
119
|
-
mixed_msg.
|
162
|
+
mixed_msg = MIME::Multipart::Mixed.new
|
163
|
+
mixed_msg.attach(img1)
|
164
|
+
mixed_msg.attach(img2)
|
120
165
|
|
121
|
-
form = MIME::
|
122
|
-
form.
|
123
|
-
form.
|
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
|
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::
|
132
|
-
htm_msg = MIME::
|
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::
|
135
|
-
msg.
|
136
|
-
msg.
|
137
|
-
assert_equal_mime_msg 'multipart_alternative
|
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.
|
187
|
+
img.transfer_encoding = 'binary'
|
143
188
|
|
144
|
-
html_msg = MIME::
|
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.
|
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.
|
199
|
+
html_msg.transfer_encoding = '7bit'
|
155
200
|
|
156
|
-
text_msg = MIME::
|
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.
|
207
|
+
text_msg.transfer_encoding = '7bit'
|
163
208
|
|
164
|
-
related_msg = MIME::
|
165
|
-
related_msg.
|
166
|
-
related_msg.
|
209
|
+
related_msg = MIME::Multipart::Related.new
|
210
|
+
related_msg.add(html_msg)
|
211
|
+
related_msg.inline(img)
|
167
212
|
|
168
|
-
msg = MIME::
|
169
|
-
msg.
|
170
|
-
msg.
|
171
|
-
assert_equal_mime_msg 'multipart_alternative_related
|
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::
|
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::
|
180
|
-
msg.
|
225
|
+
img_msg = MIME::Image.new(img_data, 'jpeg')
|
226
|
+
msg.attach(img_msg, 'filename' => img_file.path)
|
181
227
|
end
|
182
228
|
|
183
|
-
|
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::
|
193
|
-
msg.
|
194
|
-
msg.
|
195
|
-
msg.
|
196
|
-
assert_equal_mime_msg 'multipart_mixed_inline_and_attachment2
|
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.
|
246
|
+
img.transfer_encoding = 'binary'
|
202
247
|
|
203
|
-
html_msg = MIME::
|
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.
|
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.
|
260
|
+
html_msg.transfer_encoding = '7bit'
|
216
261
|
|
217
|
-
msg = MIME::
|
218
|
-
msg.
|
219
|
-
msg.
|
220
|
-
assert_equal_mime_msg 'multipart_related
|
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::
|
272
|
+
form = MIME::Multipart::FormData.new
|
228
273
|
%w(one two three four).each do |ent|
|
229
|
-
form.
|
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(
|
239
|
-
assert_match(
|
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::
|
316
|
+
form = MIME::Multipart::FormData.new
|
244
317
|
%w(one two three four).each do |ent|
|
245
|
-
form.
|
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: <(
|
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::
|
263
|
-
file5 = MIME::
|
264
|
-
file6 = MIME::
|
335
|
+
file4 = MIME::Text.new('txt')
|
336
|
+
file5 = MIME::Text.new('htm')
|
337
|
+
file6 = MIME::Text.new('xml')
|
265
338
|
|
266
|
-
form = MIME::
|
339
|
+
form = MIME::Multipart::FormData.new
|
267
340
|
# file backed objects
|
268
|
-
form.
|
269
|
-
form.
|
270
|
-
form.
|
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.
|
273
|
-
form.
|
274
|
-
form.
|
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=
|
280
|
-
assert_match(/^#{hdr} name=
|
281
|
-
assert_match(/^#{hdr} name=
|
282
|
-
assert_match(/^#{hdr} name=
|
283
|
-
assert_match(/^#{hdr} name=
|
284
|
-
assert_match(/^#{hdr} name=
|
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::
|
293
|
-
assert_raises(e) {MIME::
|
294
|
-
assert_raises(e) {MIME::
|
295
|
-
assert_raises(e) {MIME::
|
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::
|
327
|
-
assert_kind_of MIME::
|
328
|
-
assert_kind_of MIME::
|
329
|
-
assert_kind_of MIME::
|
330
|
-
assert_kind_of MIME::
|
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::
|
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::
|
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
|
-
|
395
|
-
cid = /cid
|
396
|
-
|
397
|
-
|
398
|
-
|
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(
|
403
|
-
gsub(
|
404
|
-
|
405
|
-
sub(
|
406
|
-
|
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
|
#
|
data/test/test_text_flowed.rb
CHANGED
@@ -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::
|
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
|