mime 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/README.rdoc +192 -140
  2. data/lib/mime/composite_media.rb +230 -0
  3. data/lib/mime/content_formats/text_flowed.rb +6 -6
  4. data/lib/mime/discrete_media.rb +73 -0
  5. data/lib/mime/discrete_media_factory.rb +19 -13
  6. data/lib/mime/header.rb +48 -0
  7. data/lib/mime/headers/internet.rb +124 -27
  8. data/lib/mime/headers/mime.rb +38 -27
  9. data/lib/mime/mail.rb +51 -0
  10. data/lib/mime/media.rb +30 -0
  11. data/lib/mime/parser.rb +1 -1
  12. data/lib/mime.rb +27 -9
  13. data/test/scaffold/application.msg +2 -5
  14. data/test/scaffold/audio.msg +2 -5
  15. data/test/scaffold/image.msg +0 -0
  16. data/test/scaffold/multipart_alternative.msg +7 -7
  17. data/test/scaffold/multipart_alternative_related.msg +0 -0
  18. data/test/scaffold/multipart_form_data_file_and_text.msg +0 -0
  19. data/test/scaffold/multipart_form_data_mixed.msg +0 -0
  20. data/test/scaffold/multipart_form_data_text.msg +12 -12
  21. data/test/scaffold/multipart_mixed_inline_and_attachment.msg +0 -0
  22. data/test/scaffold/multipart_mixed_inline_and_attachment2.msg +0 -0
  23. data/test/scaffold/multipart_related.msg +0 -0
  24. data/test/scaffold/rfc822_composite.msg +0 -0
  25. data/test/scaffold/{plain_text_email.msg → rfc822_discrete.msg} +5 -5
  26. data/test/scaffold/text.msg +2 -5
  27. data/test/scaffold/video.msg +2 -5
  28. data/test/test_mime.rb +323 -150
  29. data/test/test_text_flowed.rb +1 -1
  30. metadata +13 -12
  31. data/lib/mime/composite_media_type.rb +0 -169
  32. data/lib/mime/discrete_media_type.rb +0 -78
  33. data/lib/mime/header_container.rb +0 -32
  34. data/lib/mime/media_type.rb +0 -51
  35. data/lib/mime/message.rb +0 -61
data/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::CompositeMediaType for a description of composite media types
13
- * MIME::DiscreteMediaType for a description of discrete media types
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 Type Inheritance Heirarchy
19
-
20
- MediaType*
21
- ^
22
- |
23
- |--DiscreteMediaType*
24
- | ^
25
- | |
26
- | |--ApplicationMedia
27
- | |--AudioMedia
28
- | |--ImageMedia
29
- | |--TextMedia
30
- | +--VideoMedia
31
- |
32
- +--CompositeMediaType*
33
- ^
34
- |
35
- |--MessageMedia**
36
- | ^
37
- | |
38
- | |--ExternalBody**
39
- | |--Partial**
40
- | +--RFC822**
41
- |
42
- +--MultipartMedia*
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 Structure
60
-
61
-
62
- ________________ -------------------+
63
- | | |
64
- | RFC822 & MIME | |
65
- | Message Headers| |
66
- |________________| |
67
- ________________ |
68
- | | |
69
- | MIME Headers | |
70
- |~~~~~~~~~~~~~~~~| <-- MIME Entity |
71
- | Body | (N) |
72
- | (optional) | |--- RFC822 Message
73
- |________________| |
74
- ________________ |
75
- | | |
76
- | MIME Headers | |
77
- |~~~~~~~~~~~~~~~~| <-- MIME Entity |
78
- | Body | (N+1) |
79
- | (optional) | |
80
- |________________| |
81
- -------------------+
82
-
83
-
84
- Each <em>MIME Entity</em> must be a discrete (MIME::DiscreteMediaType) or
85
- composite (MIME::CompositeMediaType) media type. Because MIME is recursive,
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 DiscreteMediaType object
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 = TextMedia.new(File.read(file), 'xml')} # media class
109
- text_media = DiscreteMediaFactory.create(file) # media factory
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 = Message.new # blank message with current date and message ID headers
122
- msg.date = (Time.now - 3600).rfc2822 # change 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.add('Priority', 'urgent') # add custom header
124
+ msg.headers.set('Priority', 'urgent') # add custom header
125
125
 
126
- msg.body = TextMedia.new('hello, world!', 'plain', 'charset' => 'us-ascii')
126
+ msg.body = Text.new('hello, world!', 'plain', 'charset' => 'us-ascii')
127
127
  #
128
- # The following two snippets are equivalent to the previous line.
128
+ # The previous line is equivalent to the following snippet:
129
129
  #
130
- # msg.body = "\r\nhello, world!"
131
- # msg.header.add('Content-Type', 'text/plain; charset=us-ascii')
132
- #
133
- # --OR-- (notice the header must come first, followed by two CRLFs)
134
- #
135
- # msg.body = "Content-Type: text/plain; charset=us-ascii\r\n\r\nhello, world!"
136
-
137
- msg.to = {
138
- 'robot@example.com' => nil, # no name display
139
- 'james@example.com' => 'James Smith',
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 = MultipartMedia::Mixed.new
158
- mixed_msg.attach_entity(image)
159
- mixed_msg.add_entity(text)
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 = TextMedia.new(<<TEXT_DATA, 'plain')
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 = TextMedia.new(<<HTML_DATA, 'html')
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 = MultipartMedia::Alternative.new
185
- msg.add_entity(html_msg) # most complex representations must be added first
186
- msg.add_entity(text_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 _src_.
215
+ Notice the _src_ of the _img_ tag.
198
216
 
199
217
  image = DiscreteMediaFactory.create('/tmp/ruby.png')
200
- image.content_transfer_encoding = 'binary'
218
+ image.transfer_encoding = 'binary' # could base64 encode the image instead
201
219
 
202
- html_msg = TextMedia.new(<<EOF, 'html', 'charset' => 'iso-8859-1')
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.content_id}">
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
- html_msg.content_transfer_encoding = '7bit'
216
-
217
- related_msg = MultipartMedia::Related.new
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 = Message.new(related_msg)
222
- email_msg.to = {'joe@example.com' => 'Joe Schmo'}
223
- email_msg.from = {'john@example.com' => 'John Doe'}
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 # ready to send HTML email with image
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 = TextMedia.new('Joe Blow')
234
-
235
- portrait_filename = '/tmp/joe_portrait.jpg'
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 = MultipartMedia::FormData.new
243
- form_data.add_entity(name_field, # TextMedia object
244
- 'name') # field name, i.e. HTML input type=text
245
- form_data.add_entity(portrait_field, # ImageMedia object
246
- 'portrait', # field name, i.e. HTML input type=file
247
- portrait_filename) # suggest filename to server
248
- form_data.to_s # ready to POST via HTTP
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 = TextMedia.new('Joe Blow')
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
- img = '/tmp/joe_portrait.jpg'
260
- portrait_field = DiscreteMediaFactory.create(img) # automatic content type
261
- portrait_field.content_transfer_encoding = 'binary'
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 = TextMedia.new(flowed_txt, 'plain', 'format' => 'flowed')
288
- flowed_msg.to_s # neatly formatted text compatible with large to small screens
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