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.
- 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
|