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/README.rdoc
CHANGED
@@ -9,80 +9,80 @@ multipart/form-data transactions.
|
|
9
9
|
== See
|
10
10
|
|
11
11
|
* MIME for RFCs used to implement the library (other RFCs scattered throughout)
|
12
|
-
* MIME::
|
13
|
-
* MIME::
|
12
|
+
* MIME::CompositeMedia for a description of composite media types
|
13
|
+
* MIME::DiscreteMedia for a description of discrete media types
|
14
14
|
* MIME::DiscreteMediaFactory for easy programming of discrete media types
|
15
15
|
* MIME::ContentFormats for ways to encode/decode discrete media types
|
16
16
|
|
17
17
|
|
18
|
-
== Media
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
== MIME Message
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
Each <em>MIME Entity</em> must be a discrete (MIME::
|
85
|
-
composite (MIME::
|
18
|
+
== Media Inheritance Heirarchy
|
19
|
+
|
20
|
+
Media*
|
21
|
+
^
|
22
|
+
|
|
23
|
+
|--DiscreteMedia*
|
24
|
+
| ^
|
25
|
+
| |
|
26
|
+
| |--Application
|
27
|
+
| |--Audio
|
28
|
+
| |--Image
|
29
|
+
| |--Text
|
30
|
+
| +--Video
|
31
|
+
|
|
32
|
+
+--CompositeMedia*
|
33
|
+
^
|
34
|
+
|
|
35
|
+
|--Message**
|
36
|
+
| ^
|
37
|
+
| |
|
38
|
+
| |--ExternalBody**
|
39
|
+
| |--Partial**
|
40
|
+
| +--RFC822**
|
41
|
+
|
|
42
|
+
+--Multipart*
|
43
|
+
^
|
44
|
+
|
|
45
|
+
|--Alternative
|
46
|
+
|--Digest**
|
47
|
+
|--Encrypted**
|
48
|
+
|--FormData
|
49
|
+
|--Mixed
|
50
|
+
|--Parallel**
|
51
|
+
|--Related
|
52
|
+
|--Report**
|
53
|
+
+--Signed**
|
54
|
+
|
55
|
+
* Abstract Class
|
56
|
+
** Not implemented
|
57
|
+
|
58
|
+
|
59
|
+
== MIME Message Format
|
60
|
+
|
61
|
+
|
62
|
+
________________ -------------------+
|
63
|
+
| | |
|
64
|
+
| RFC822 & MIME | |
|
65
|
+
| Headers | |
|
66
|
+
|________________| |
|
67
|
+
________________ |
|
68
|
+
| | |
|
69
|
+
| MIME Headers | |
|
70
|
+
|~~~~~~~~~~~~~~~~| <-- MIME Entity* |--- RFC822/MIME
|
71
|
+
| Body* | (N) | Message
|
72
|
+
|________________| |
|
73
|
+
________________ |
|
74
|
+
| | |
|
75
|
+
| MIME Headers | |
|
76
|
+
|~~~~~~~~~~~~~~~~| <-- MIME Entity* |
|
77
|
+
| Body* | (N+1) |
|
78
|
+
|________________| |
|
79
|
+
-------------------+
|
80
|
+
|
81
|
+
* Optional
|
82
|
+
|
83
|
+
|
84
|
+
Each <em>MIME Entity</em> must be a discrete (MIME::DiscreteMedia) or
|
85
|
+
composite (MIME::CompositeMedia) media type. Because MIME is recursive,
|
86
86
|
composite entity bodies may contain other composite or discrete entities and so
|
87
87
|
on. However, discrete entities are non-recursive and contain only non-MIME
|
88
88
|
bodies.
|
@@ -96,7 +96,7 @@ bodies.
|
|
96
96
|
include MIME # allow ommision of "MIME::" namespace in examples below
|
97
97
|
|
98
98
|
|
99
|
-
=== Instantiate a
|
99
|
+
=== Instantiate a DiscreteMedia object
|
100
100
|
|
101
101
|
Discrete media objects, such as text or video, can be created directly using a
|
102
102
|
specific discrete media *class* or indirectly via the *factory*. If the media is
|
@@ -105,8 +105,8 @@ file and determine the MIME type for you.
|
|
105
105
|
|
106
106
|
file = '/tmp/data.xml'
|
107
107
|
|
108
|
-
text_media =
|
109
|
-
text_media = DiscreteMediaFactory.create(file)
|
108
|
+
text_media = Text.new(File.read(file), 'xml') # media class
|
109
|
+
text_media = DiscreteMediaFactory.create(file) # media factory
|
110
110
|
|
111
111
|
Discrete media objects can then be embedded in MIME messages as we will see in
|
112
112
|
the next example.
|
@@ -118,34 +118,52 @@ Create a well-formed email message with multiple recipients. The string
|
|
118
118
|
representation of the message (i.e. to_s) can then be sent directly via an
|
119
119
|
SMTP client.
|
120
120
|
|
121
|
-
msg =
|
122
|
-
msg.date
|
121
|
+
msg = Mail.new # blank message with current date and message ID headers
|
122
|
+
msg.date -= 3600 # change date to 1 hour ago
|
123
123
|
msg.subject = 'This is important' # add subject
|
124
|
-
msg.headers.
|
124
|
+
msg.headers.set('Priority', 'urgent') # add custom header
|
125
125
|
|
126
|
-
msg.body =
|
126
|
+
msg.body = Text.new('hello, world!', 'plain', 'charset' => 'us-ascii')
|
127
127
|
#
|
128
|
-
# The
|
128
|
+
# The previous line is equivalent to the following snippet:
|
129
129
|
#
|
130
|
-
# msg.body =
|
131
|
-
# msg.
|
132
|
-
|
133
|
-
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
'
|
139
|
-
'
|
140
|
-
'clint@example.com' => 'Clint Pachl',
|
141
|
-
}
|
142
|
-
msg.from = {
|
143
|
-
'boss@example.com' => 'Boss Man'
|
130
|
+
# msg.body = 'hello, world!'
|
131
|
+
# msg.headers.set('Content-Type', 'text/plain; charset=us-ascii')
|
132
|
+
|
133
|
+
msg.from = {'boss@example.com' => 'Boss Man'} # mailbox hash
|
134
|
+
msg.bcc = 'boss+home@example.com' # mailbox string
|
135
|
+
msg.cc = %w(secretary@example.com manager@example.com) # mailbox array
|
136
|
+
msg.to = {
|
137
|
+
'list@example.com' => nil, # no name display
|
138
|
+
'john@example.com' => 'John Doe',
|
139
|
+
'jane@example.com' => 'Jane Doe',
|
144
140
|
}
|
145
141
|
|
146
142
|
msg.to_s # ready to be sent via SMTP
|
147
143
|
|
148
144
|
|
145
|
+
=== RFC822 email message with image body
|
146
|
+
|
147
|
+
Embedding a single image within an email message requires a single discrete
|
148
|
+
media image. However, embedding multiple images requires a multipart/mixed
|
149
|
+
composite media type to encapsulate all of the discrete media images.
|
150
|
+
|
151
|
+
==== Email body with single image
|
152
|
+
|
153
|
+
img = Image.new(File.read('screenshot.png'), 'png')
|
154
|
+
img.disposition = 'inline'
|
155
|
+
img.description = 'My screenshot'
|
156
|
+
email = Mail.new(img)
|
157
|
+
|
158
|
+
==== Email body with multiple images
|
159
|
+
|
160
|
+
msg = Multipart::Mixed.new
|
161
|
+
msg.inline Image.new(File.read('screenshot1.png'), 'png')
|
162
|
+
msg.inline Image.new(File.read('screenshot2.png'), 'png')
|
163
|
+
msg.description = 'My screenshots'
|
164
|
+
email = Mail.new(msg)
|
165
|
+
|
166
|
+
|
149
167
|
=== Plain text multipart/mixed message with a file attachment
|
150
168
|
|
151
169
|
The multipart/mixed content type can be used to aggregate multiple unrelated
|
@@ -154,9 +172,9 @@ entities, such as text and an image.
|
|
154
172
|
text = DiscreteMediaFactory.create('/tmp/data.txt')
|
155
173
|
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
156
174
|
|
157
|
-
mixed_msg =
|
158
|
-
mixed_msg.
|
159
|
-
mixed_msg.
|
175
|
+
mixed_msg = Multipart::Mixed.new
|
176
|
+
mixed_msg.add(text)
|
177
|
+
mixed_msg.attach(image)
|
160
178
|
mixed_msg.to_s
|
161
179
|
|
162
180
|
|
@@ -166,13 +184,13 @@ The multipart/alternative content type allows for multiple, alternatively
|
|
166
184
|
formatted versions of the same content, such as plain text and HTML. Clients are
|
167
185
|
then responsible for choosing the most suitable version for display.
|
168
186
|
|
169
|
-
text_msg =
|
187
|
+
text_msg = Text.new(<<TEXT_DATA, 'plain')
|
170
188
|
**Hello, world!**
|
171
189
|
|
172
190
|
Ruby is cool!
|
173
191
|
TEXT_DATA
|
174
192
|
|
175
|
-
html_msg =
|
193
|
+
html_msg = Text.new(<<HTML_DATA, 'html')
|
176
194
|
<html>
|
177
195
|
<body>
|
178
196
|
<h1>Hello, world!</h1>
|
@@ -181,10 +199,10 @@ then responsible for choosing the most suitable version for display.
|
|
181
199
|
</html>
|
182
200
|
HTML_DATA
|
183
201
|
|
184
|
-
msg =
|
185
|
-
msg.
|
186
|
-
msg.
|
187
|
-
msg.to_s
|
202
|
+
msg = Multipart::Alternative.new
|
203
|
+
msg.add(text_msg) # add the simplest representations first
|
204
|
+
msg.add(html_msg)
|
205
|
+
msg.to_s # or send in an email: Mail.new(msg)
|
188
206
|
|
189
207
|
|
190
208
|
=== HTML multipart/related MIME email with embedded image
|
@@ -194,35 +212,33 @@ parts. For example, an HTML page with embedded images. The multipart/related
|
|
194
212
|
content type aggregates all the parts and creates the means for the root entity
|
195
213
|
to reference the other entities.
|
196
214
|
|
197
|
-
Notice the _img_ tag
|
215
|
+
Notice the _src_ of the _img_ tag.
|
198
216
|
|
199
217
|
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
200
|
-
image.
|
218
|
+
image.transfer_encoding = 'binary' # could base64 encode the image instead
|
201
219
|
|
202
|
-
html_msg =
|
220
|
+
html_msg = Text.new(<<EOF, 'html', 'charset' => 'iso-8859-1')
|
203
221
|
<html>
|
204
222
|
<body>
|
205
223
|
<h1>Ruby Image</h1>
|
206
224
|
<p>
|
207
225
|
Check out this cool pic.
|
208
|
-
<img alt="ruby is cool" src="cid:#{image.
|
226
|
+
<img alt="ruby is cool" src="cid:#{image.id}">
|
209
227
|
</p>
|
210
228
|
<p>Wasn't it cool?</p>
|
211
229
|
</body>
|
212
230
|
</html>
|
213
231
|
EOF
|
214
232
|
|
215
|
-
|
216
|
-
|
217
|
-
related_msg
|
218
|
-
related_msg.inline_entity(image)
|
219
|
-
related_msg.add_entity(html_msg)
|
233
|
+
related_msg = Multipart::Related.new
|
234
|
+
related_msg.add(html_msg)
|
235
|
+
related_msg.inline(image)
|
220
236
|
|
221
|
-
email_msg
|
222
|
-
email_msg.to
|
223
|
-
email_msg.from
|
237
|
+
email_msg = Mail.new(related_msg)
|
238
|
+
email_msg.to = 'jane@example.com'
|
239
|
+
email_msg.from = 'john@example.com'
|
224
240
|
email_msg.subject = 'Ruby is cool, checkout the picture'
|
225
|
-
email_msg.to_s
|
241
|
+
email_msg.to_s # ready to send HTML email with image
|
226
242
|
|
227
243
|
|
228
244
|
=== HTML form with file upload using multipart/form-data encoding
|
@@ -230,39 +246,34 @@ Notice the _img_ tag _src_.
|
|
230
246
|
This example builds a representation of an HTML form that can be POSTed to an
|
231
247
|
HTTP server. It contains a single text input and a file input.
|
232
248
|
|
233
|
-
name_field
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
portrait_field = open(portrait_filename) do |f|
|
238
|
-
ImageMedia.new(f.read, 'jpeg') # explicit content type
|
239
|
-
end
|
240
|
-
portrait_field.content_transfer_encoding = 'binary'
|
249
|
+
name_field = Text.new('Joe Blow')
|
250
|
+
portrait_path = '/tmp/joe_portrait.jpg'
|
251
|
+
portrait_field = Image.new(File.read(portrait_path), 'jpeg')
|
252
|
+
portrait_field.transfer_encoding = 'binary'
|
241
253
|
|
242
|
-
form_data =
|
243
|
-
form_data.
|
244
|
-
|
245
|
-
form_data.
|
246
|
-
|
247
|
-
|
248
|
-
form_data.to_s
|
254
|
+
form_data = Multipart::FormData.new
|
255
|
+
form_data.add(name_field, # field value
|
256
|
+
'name') # field name, i.e. HTML input type=text
|
257
|
+
form_data.add(portrait_field, # field value
|
258
|
+
'portrait', # field name, i.e. HTML input type=file
|
259
|
+
portrait_path) # suggest image filename to server
|
260
|
+
form_data.to_s # ready to POST via HTTP
|
249
261
|
|
250
262
|
|
251
263
|
=== HTML form with file upload via DiscreteMediaFactory
|
252
264
|
|
253
265
|
The outcome of this example is identical to the previous one. The only semantic
|
254
266
|
difference is that the DiscreteMediaFactory module is used to instantiate the
|
255
|
-
image object.
|
267
|
+
image object and automatically set the content type and FormData filename.
|
256
268
|
|
257
|
-
name_field
|
269
|
+
name_field = Text.new('Joe Blow')
|
270
|
+
portrait_path = '/tmp/joe_portrait.jpg'
|
271
|
+
portrait_field = DiscreteMediaFactory.create(portrait_path)
|
272
|
+
portrait_field.transfer_encoding = 'binary'
|
258
273
|
|
259
|
-
|
260
|
-
|
261
|
-
portrait_field
|
262
|
-
|
263
|
-
form_data = MultipartMedia::FormData.new
|
264
|
-
form_data.add_entity(name_field, 'name')
|
265
|
-
form_data.add_entity(portrait_field, 'portrait') # automatic file name
|
274
|
+
form_data = Multipart::FormData.new
|
275
|
+
form_data.add(name_field, 'name')
|
276
|
+
form_data.add(portrait_field, 'portrait')
|
266
277
|
form_data.to_s
|
267
278
|
|
268
279
|
|
@@ -284,19 +295,27 @@ appropriate.
|
|
284
295
|
"80 characters and will be soft line wrapped after the word '80'.\n\n"
|
285
296
|
|
286
297
|
flowed_txt = ContentFormats::TextFlowed.encode(long_paragraph * 2)
|
287
|
-
flowed_msg =
|
288
|
-
flowed_msg.to_s # neatly formatted text compatible with
|
298
|
+
flowed_msg = Text.new(flowed_txt, 'plain', 'format' => 'flowed')
|
299
|
+
flowed_msg.to_s # neatly formatted text compatible with small to large screens
|
289
300
|
|
290
301
|
|
291
302
|
== More Examples
|
292
303
|
|
293
304
|
For many more examples, check the test class MIMETest.
|
294
305
|
|
306
|
+
|
295
307
|
== Links
|
296
308
|
|
297
309
|
Ruby Gem :: https://rubygems.org/gems/mime
|
298
310
|
Source Code :: https://bitbucket.org/pachl/mime/src
|
299
311
|
Documentation :: http://ecentryx.com/gems/mime
|
312
|
+
Validators :: Please check *all* of your messages using the following lint
|
313
|
+
tools and report errors to pachl@ecentryx.com with "Ruby MIME"
|
314
|
+
in the subject.
|
315
|
+
|
316
|
+
- http://www.apps.ietf.org/content/message-lint
|
317
|
+
- http://tools.ietf.org/tools/msglint
|
318
|
+
|
300
319
|
|
301
320
|
== History
|
302
321
|
|
@@ -313,6 +332,39 @@ Documentation :: http://ecentryx.com/gems/mime
|
|
313
332
|
* Simplify API of DiscreteMediaType subclasses.
|
314
333
|
* Disallow Content-Type changes after instantiating DiscreteMediaType.
|
315
334
|
* Add flowed format support for text/plain (RFC 2646).
|
335
|
+
4. 2014-04-18, v0.4.0
|
336
|
+
* <b>Major API disruption!</b>
|
337
|
+
* Rename classes:
|
338
|
+
- HeaderContainter => Header
|
339
|
+
- remove "Media" suffix from the 5 DiscreteMedia and 2 CompositeMedia
|
340
|
+
subclasses. See revision 4876b1390624 for details.
|
341
|
+
- MIME::Message => MIME::Mail
|
342
|
+
* Rename methods:
|
343
|
+
- remove "_entity" suffix from add, inline, and attach in CompositeMedia.
|
344
|
+
- remove "content_" prefix from id, disposition, description, and
|
345
|
+
transfer_encoding in Headers::MIME.
|
346
|
+
* Remove methods:
|
347
|
+
- Header#add
|
348
|
+
* Add methods:
|
349
|
+
- Header#set (replace Header#add)
|
350
|
+
- Header#get
|
351
|
+
- Header#delete
|
352
|
+
* Reverse order of entities in CompositeMedia::Body, which most likely
|
353
|
+
reverses all CompositeMedia #add, #inline, and #attach method calls.
|
354
|
+
See revision a51745f346b5 for details.
|
355
|
+
* Other changes
|
356
|
+
* Use From header field domain in the Message-ID header.
|
357
|
+
* Add more randomness when generating header IDs.
|
358
|
+
* Header field names are now case-insensitive to comply with RFCs.
|
359
|
+
* Accept String, Array, and Hash for originator and destination mailboxes.
|
360
|
+
* Add CompositeMedia::Body class for nesting MIME entities.
|
361
|
+
* Improve docs and examples for Content-Disposition (inline/attachment).
|
362
|
+
* FIX: remove trailing CRLF in Mail#to_s and update tests.
|
363
|
+
* Add README links to message lint tools on IETF.org.
|
364
|
+
* Add Send, In-Reply-To, and References RFC 5322 header fields.
|
365
|
+
* Comply with RFC regarding parameter quoting in header field bodies.
|
366
|
+
I.e., do not quote atom/dot-atom parameter values.
|
367
|
+
* Many fixes and improvements in code, tests, documentation, and examples.
|
316
368
|
|
317
369
|
|
318
370
|
== License
|