mime 0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +303 -0
- data/Rakefile +8 -24
- data/lib/mime.rb +1 -1
- data/lib/mime/discrete_media_factory.rb +2 -3
- data/lib/mime/headers/internet.rb +15 -13
- data/lib/mime/headers/mime.rb +18 -18
- data/lib/mime/message.rb +10 -0
- data/mime.gemspec +18 -0
- data/test/scaffold/application.msg +4 -4
- data/test/scaffold/audio.msg +3 -3
- data/test/scaffold/{index.html → data.htm} +0 -0
- data/test/scaffold/image.msg +0 -0
- data/test/scaffold/multipart_alternative.msg +10 -12
- 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 +31 -26
- 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/plain_text_email.msg +9 -7
- data/test/scaffold/text.msg +2 -2
- data/test/scaffold/video.msg +4 -4
- data/test/test_mime.rb +417 -0
- data/test/test_mime.rb-try +616 -0
- metadata +53 -51
- data/README +0 -256
- data/test/mime_test.rb +0 -386
- data/test/scaffold/multipart_form_data_file.msg +0 -0
data/README.rdoc
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
== Multipurpose Internet Mail Extensions (MIME)
|
2
|
+
|
3
|
+
A library for building RFC compliant Multipurpose Internet Mail Extensions
|
4
|
+
(MIME) messages. It can be used to construct standardized MIME messages for use
|
5
|
+
in client/server communications, such as Internet mail or HTTP
|
6
|
+
multipart/form-data transactions.
|
7
|
+
|
8
|
+
|
9
|
+
== See
|
10
|
+
|
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
|
14
|
+
* MIME::DiscreteMediaFactory for easy programming of discrete media types
|
15
|
+
|
16
|
+
|
17
|
+
== Media Type Inheritance Heirarchy
|
18
|
+
|
19
|
+
MediaType*
|
20
|
+
^
|
21
|
+
|
|
22
|
+
|--DiscreteMediaType*
|
23
|
+
| ^
|
24
|
+
| |
|
25
|
+
| |--ApplicationMedia
|
26
|
+
| |--AudioMedia
|
27
|
+
| |--ImageMedia
|
28
|
+
| |--TextMedia
|
29
|
+
| +--VideoMedia
|
30
|
+
|
|
31
|
+
+--CompositeMediaType*
|
32
|
+
^
|
33
|
+
|
|
34
|
+
|--MessageMedia**
|
35
|
+
| ^
|
36
|
+
| |
|
37
|
+
| |--ExternalBody**
|
38
|
+
| |--Partial**
|
39
|
+
| +--RFC822**
|
40
|
+
|
|
41
|
+
+--MultipartMedia*
|
42
|
+
^
|
43
|
+
|
|
44
|
+
|--Alternative
|
45
|
+
|--Digest**
|
46
|
+
|--Encrypted**
|
47
|
+
|--FormData
|
48
|
+
|--Mixed
|
49
|
+
|--Parallel**
|
50
|
+
|--Related
|
51
|
+
|--Report**
|
52
|
+
+--Signed**
|
53
|
+
|
54
|
+
* Abstract Class
|
55
|
+
** Not implemented
|
56
|
+
|
57
|
+
|
58
|
+
== MIME Message Structure
|
59
|
+
|
60
|
+
|
61
|
+
________________ -------------------+
|
62
|
+
| | |
|
63
|
+
| RFC822 & MIME | |
|
64
|
+
| Message Headers| |
|
65
|
+
|________________| |
|
66
|
+
________________ |
|
67
|
+
| | |
|
68
|
+
| MIME Headers | |
|
69
|
+
|~~~~~~~~~~~~~~~~| <-- MIME Entity |
|
70
|
+
| Body | (N) |
|
71
|
+
| (optional) | |--- RFC822 Message
|
72
|
+
|________________| |
|
73
|
+
________________ |
|
74
|
+
| | |
|
75
|
+
| MIME Headers | |
|
76
|
+
|~~~~~~~~~~~~~~~~| <-- MIME Entity |
|
77
|
+
| Body | (N+1) |
|
78
|
+
| (optional) | |
|
79
|
+
|________________| |
|
80
|
+
-------------------+
|
81
|
+
|
82
|
+
|
83
|
+
Each <em>MIME Entity</em> must be a discrete (MIME::DiscreteMediaType) or
|
84
|
+
composite (MIME::CompositeMediaType) media type. Because MIME is recursive,
|
85
|
+
composite entity bodies may contain other composite or discrete entities and so
|
86
|
+
on. However, discrete entities are non-recursive and contain only non-MIME
|
87
|
+
bodies.
|
88
|
+
|
89
|
+
|
90
|
+
== Examples
|
91
|
+
|
92
|
+
<em>First things first!</em>
|
93
|
+
|
94
|
+
require 'mime'
|
95
|
+
|
96
|
+
|
97
|
+
=== Instantiate a DiscreteMediaType object
|
98
|
+
|
99
|
+
Discrete media objects, such as text or video, can be created directly using a
|
100
|
+
specific discrete media *class* or indirectly via the *factory*. If the media is
|
101
|
+
file backed, like the example below, the factory will open and read the data
|
102
|
+
file and determine the MIME type for you.
|
103
|
+
|
104
|
+
file = '/tmp/data.xml'
|
105
|
+
|
106
|
+
text_media = TextMedia.new(File.read(file), 'text/xml')} # media class
|
107
|
+
text_media = DiscreteMediaFactory.create(file) # media factory
|
108
|
+
|
109
|
+
Discrete media objects can then be embedded in MIME messages as we will see in
|
110
|
+
the next example.
|
111
|
+
|
112
|
+
|
113
|
+
=== Simple text/plain RFC822 email message
|
114
|
+
|
115
|
+
Create a well-formed email message with multiple recipients. The string
|
116
|
+
representation of the message (i.e. to_s) can then be sent directly via an
|
117
|
+
SMTP client.
|
118
|
+
|
119
|
+
msg = Message.new # blank message with current date and message ID headers
|
120
|
+
msg.date = (Time.now - 3600).rfc2822 # change date
|
121
|
+
msg.subject = 'This is important' # add subject
|
122
|
+
msg.headers.add('Priority', 'urgent') # add custom header
|
123
|
+
|
124
|
+
msg.body = TextMedia.new('hello, world!')
|
125
|
+
#
|
126
|
+
# The following two snippets are equivalent to the previous line.
|
127
|
+
#
|
128
|
+
# msg.body = "\r\nhello, world!"
|
129
|
+
# msg.header.add('Content-Type', 'text/plain; charset=us-ascii')
|
130
|
+
#
|
131
|
+
# --OR-- (notice the header must come first, followed by CRLFx2)
|
132
|
+
#
|
133
|
+
# msg.body = "Content-Type: text/plain; charset=us-ascii\r\n\r\nhello, world!"
|
134
|
+
|
135
|
+
msg.to = {
|
136
|
+
'robot@example.com' => nil, # no name display
|
137
|
+
'james@example.com' => 'James Smith',
|
138
|
+
'clint@example.com' => 'Clint Pachl',
|
139
|
+
}
|
140
|
+
msg.from = {
|
141
|
+
'boss@example.com' => 'Boss Man'
|
142
|
+
}
|
143
|
+
|
144
|
+
msg.to_s # ready to be sent via SMTP
|
145
|
+
|
146
|
+
|
147
|
+
=== Plain text multipart/mixed message with a file attachment
|
148
|
+
|
149
|
+
The multipart/mixed content type can be used to aggregate multiple unrelated
|
150
|
+
entities, such as text and an image.
|
151
|
+
|
152
|
+
text = DiscreteMediaFactory.create('/tmp/data.txt')
|
153
|
+
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
154
|
+
|
155
|
+
mixed_msg = MultipartMedia::Mixed.new
|
156
|
+
mixed_msg.attach_entity(image)
|
157
|
+
mixed_msg.add_entity(text)
|
158
|
+
mixed_msg.to_s
|
159
|
+
|
160
|
+
|
161
|
+
=== Plain text and HTML multipart/alternative MIME message
|
162
|
+
|
163
|
+
The multipart/alternative content type allows for multiple, alternatively
|
164
|
+
formatted versions of the same content, such as plain text and HTML. Clients are
|
165
|
+
then responsible for choosing the most suitable version for display.
|
166
|
+
|
167
|
+
text_msg = TextMedia.new(<<TEXT_DATA, 'text/plain')
|
168
|
+
**Hello, world!**
|
169
|
+
|
170
|
+
Ruby is cool!
|
171
|
+
TEXT_DATA
|
172
|
+
|
173
|
+
html_msg = TextMedia.new(<<HTML_DATA, 'text/html')
|
174
|
+
<html>
|
175
|
+
<body>
|
176
|
+
<h1>Hello, world!</h1>
|
177
|
+
<p>Ruby is cool!</p>
|
178
|
+
</body>
|
179
|
+
</html>
|
180
|
+
HTML_DATA
|
181
|
+
|
182
|
+
msg = MultipartMedia::Alternative.new
|
183
|
+
msg.add_entity(html_msg) # most complex representations must be added first
|
184
|
+
msg.add_entity(text_msg)
|
185
|
+
msg.to_s
|
186
|
+
|
187
|
+
|
188
|
+
=== HTML multipart/related MIME email with embedded image
|
189
|
+
|
190
|
+
Sometimes it is desirable to send a document that is made up of many separate
|
191
|
+
parts. For example, an HTML page with embedded images. The multipart/related
|
192
|
+
content type aggregates all the parts and creates the means for the root entity
|
193
|
+
to reference the other entities.
|
194
|
+
|
195
|
+
Notice the _img_ tag _src_.
|
196
|
+
|
197
|
+
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
198
|
+
image.content_transfer_encoding = 'binary'
|
199
|
+
|
200
|
+
html_msg = TextMedia.new(<<EOF, 'text/html; charset=iso-8859-1')
|
201
|
+
<html>
|
202
|
+
<body>
|
203
|
+
<h1>Ruby Image</h1>
|
204
|
+
<p>
|
205
|
+
Check out this cool pic.
|
206
|
+
<img alt="ruby is cool" src="cid:#{image.content_id}">
|
207
|
+
</p>
|
208
|
+
<p>Wasn't it cool?</p>
|
209
|
+
</body>
|
210
|
+
</html>
|
211
|
+
EOF
|
212
|
+
|
213
|
+
html_msg.content_transfer_encoding = '7bit'
|
214
|
+
|
215
|
+
related_msg = MultipartMedia::Related.new
|
216
|
+
related_msg.inline_entity(image)
|
217
|
+
related_msg.add_entity(html_msg)
|
218
|
+
|
219
|
+
email_msg = Message.new(related_msg)
|
220
|
+
email_msg.to = {'joe@example.com' => 'Joe Schmo'}
|
221
|
+
email_msg.from = {'john@example.com' => 'John Doe'}
|
222
|
+
email_msg.subject = 'Ruby is cool, checkout the picture'
|
223
|
+
email_msg.to_s # ready to send HTML email with image
|
224
|
+
|
225
|
+
|
226
|
+
=== HTML form with file upload using multipart/form-data encoding
|
227
|
+
|
228
|
+
This example builds a representation of an HTML form that can be POSTed to an
|
229
|
+
HTTP server. It contains a single text input and a file input.
|
230
|
+
|
231
|
+
name_field = TextMedia.new('Joe Blow')
|
232
|
+
|
233
|
+
portrait_filename = '/tmp/joe_portrait.jpg'
|
234
|
+
|
235
|
+
portrait_field = open(portrait_filename) do |f|
|
236
|
+
ImageMedia.new(f.read, 'image/jpeg') # explicit content type
|
237
|
+
end
|
238
|
+
portrait_field.content_transfer_encoding = 'binary'
|
239
|
+
|
240
|
+
form_data = MultipartMedia::FormData.new
|
241
|
+
form_data.add_entity(name_field, # TextMedia object
|
242
|
+
'name') # field name, i.e. HTML input type=text
|
243
|
+
form_data.add_entity(portrait_field, # ImageMedia object
|
244
|
+
'portrait', # field name, i.e. HTML input type=file
|
245
|
+
portrait_filename) # suggest filename to server
|
246
|
+
form_data.to_s # ready to POST via HTTP
|
247
|
+
|
248
|
+
|
249
|
+
=== HTML form with file upload via DiscreteMediaFactory
|
250
|
+
|
251
|
+
The outcome of this example is identical to the previous one. The only semantic
|
252
|
+
difference is that the DiscreteMediaFactory class is used to instantiate the
|
253
|
+
image object.
|
254
|
+
|
255
|
+
name_field = TextMedia.new('Joe Blow')
|
256
|
+
|
257
|
+
img = '/tmp/joe_portrait.jpg'
|
258
|
+
portrait_field = DiscreteMediaFactory.create(img) # automatic content type
|
259
|
+
portrait_field.content_transfer_encoding = 'binary'
|
260
|
+
|
261
|
+
form_data = MultipartMedia::FormData.new
|
262
|
+
form_data.add_entity(name_field, 'name')
|
263
|
+
form_data.add_entity(portrait_field, 'portrait') # automatic file name
|
264
|
+
form_data.to_s
|
265
|
+
|
266
|
+
|
267
|
+
== More Examples
|
268
|
+
|
269
|
+
For many more examples, check the test class MIMETest.
|
270
|
+
|
271
|
+
== Links
|
272
|
+
|
273
|
+
Ruby Gem :: https://rubygems.org/gems/mime
|
274
|
+
Source Code :: https://bitbucket.org/pachl/mime/src
|
275
|
+
Documentation :: http://ecentryx.com/gems/mime
|
276
|
+
|
277
|
+
== History
|
278
|
+
|
279
|
+
1. 2008-11-05, v0.1: First public release
|
280
|
+
2. 2013-11-17, v0.2.0: Update for Ruby 1.9.3
|
281
|
+
* Update Rakefile test, package, and rdoc tasks.
|
282
|
+
* Change test suite from Test::Unit to Minitest.
|
283
|
+
* Cleanup existing and add new tests cases.
|
284
|
+
* Clarify code comments and README examples.
|
285
|
+
* Fix content type detection.
|
286
|
+
|
287
|
+
== License
|
288
|
+
|
289
|
+
({ISC License}[http://opensource.org/licenses/ISC])
|
290
|
+
|
291
|
+
Copyright (c) 2014, Clint Pachl <pachl@ecentryx.com>
|
292
|
+
|
293
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
294
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
295
|
+
and this permission notice appear in all copies.
|
296
|
+
|
297
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
298
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
299
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
300
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
301
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
302
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
303
|
+
THIS SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,34 +1,18 @@
|
|
1
|
-
require '
|
1
|
+
require 'rdoc/task'
|
2
|
+
require 'rubygems/package_task'
|
2
3
|
require 'rake/testtask'
|
3
|
-
require 'rake/gempackagetask'
|
4
4
|
|
5
|
-
|
6
|
-
s.name = 'mime'
|
7
|
-
s.version = '0.1'
|
8
|
-
s.summary = 'Multipurpose Internet Mail Extensions (MIME) Library'
|
9
|
-
s.test_files = FileList['test/*.rb']
|
10
|
-
s.files = FileList['README', 'Rakefile', 'lib/**/*.rb', 'test/**/*']
|
11
|
-
s.author = 'Clint Pachl'
|
12
|
-
s.email = 'pachl@ecentryx.com'
|
13
|
-
s.homepage = 'mime.rubyforge.org'
|
14
|
-
s.rubyforge_project = 'mime'
|
15
|
-
s.has_rdoc = true
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
Rake::GemPackageTask.new(gem_spec) do |pkg|
|
5
|
+
Gem::PackageTask.new(eval File.read('mime.gemspec')) do |pkg|
|
20
6
|
pkg.need_tar_gz = true
|
21
7
|
end
|
22
8
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
9
|
+
RDoc::Task.new do |rdoc|
|
10
|
+
rdoc.main = 'README.rdoc'
|
11
|
+
rdoc.rdoc_dir = 'doc'
|
12
|
+
rdoc.rdoc_files.include('README.rdoc', 'lib/')
|
13
|
+
rdoc.options << "--all"
|
28
14
|
end
|
29
15
|
|
30
16
|
Rake::TestTask.new do |t|
|
31
|
-
t.libs << 'lib'
|
32
|
-
t.pattern = 'test/*_test.rb'
|
33
17
|
t.warning = true
|
34
18
|
end
|
data/lib/mime.rb
CHANGED
@@ -42,15 +42,14 @@ module MIME
|
|
42
42
|
fname = file
|
43
43
|
end
|
44
44
|
|
45
|
-
raise UnknownContentError unless ctype
|
46
|
-
|
47
45
|
media_obj =
|
48
|
-
case ctype.
|
46
|
+
case ctype.to_s[/^(\w+)\//, 1]
|
49
47
|
when 'application'; ApplicationMedia.new(cntnt, ctype)
|
50
48
|
when 'audio' ; AudioMedia.new(cntnt, ctype)
|
51
49
|
when 'image' ; ImageMedia.new(cntnt, ctype)
|
52
50
|
when 'text' ; TextMedia.new(cntnt, ctype)
|
53
51
|
when 'video' ; VideoMedia.new(cntnt, ctype)
|
52
|
+
else raise UnknownContentError, "invalid content type: #{ctype}"
|
54
53
|
end
|
55
54
|
|
56
55
|
class << media_obj; attr_accessor :path end
|
@@ -6,14 +6,21 @@ module MIME
|
|
6
6
|
#
|
7
7
|
module Internet
|
8
8
|
|
9
|
-
attr_reader
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
attr_reader(
|
10
|
+
# Required Headers
|
11
|
+
:to,
|
12
|
+
:from,
|
13
|
+
:date,
|
14
|
+
|
15
|
+
# Optional Headers
|
16
|
+
:cc,
|
17
|
+
:bcc,
|
18
|
+
:reply_to,
|
19
|
+
:message_id,
|
20
|
+
:comments,
|
21
|
+
:keywords,
|
22
|
+
:subject
|
23
|
+
)
|
17
24
|
|
18
25
|
def date= date
|
19
26
|
@date = date
|
@@ -30,11 +37,6 @@ module MIME
|
|
30
37
|
headers.add('To', @to)
|
31
38
|
end
|
32
39
|
|
33
|
-
|
34
|
-
#
|
35
|
-
# Optional Headers
|
36
|
-
#
|
37
|
-
|
38
40
|
def cc= list
|
39
41
|
@cc = stringify_email_list(list)
|
40
42
|
headers.add('Cc', @cc)
|
data/lib/mime/headers/mime.rb
CHANGED
@@ -6,13 +6,14 @@ module MIME
|
|
6
6
|
#
|
7
7
|
module MIME
|
8
8
|
|
9
|
-
attr_reader
|
9
|
+
attr_reader(
|
10
|
+
:mime_version,
|
10
11
|
:content_description,
|
11
12
|
:content_disposition,
|
12
13
|
:content_id,
|
13
14
|
:content_transfer_encoding,
|
14
15
|
:content_type
|
15
|
-
|
16
|
+
)
|
16
17
|
|
17
18
|
#
|
18
19
|
# Describes the content, which can be useful for non-MIME clients.
|
@@ -47,11 +48,11 @@ module MIME
|
|
47
48
|
# The mechanism used for encoding the top-level message content.
|
48
49
|
#
|
49
50
|
# Common Encoding Mechanisms
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
51
|
+
# * 7bit
|
52
|
+
# * 8bit
|
53
|
+
# * binary
|
54
|
+
# * quoted-printable
|
55
|
+
# * base64
|
55
56
|
#
|
56
57
|
def content_transfer_encoding= encoding
|
57
58
|
@content_transfer_encoding = encoding
|
@@ -63,11 +64,11 @@ module MIME
|
|
63
64
|
# the form <em>media-type/subtype</em>.
|
64
65
|
#
|
65
66
|
# Common Content Types
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
67
|
+
# * application/octet-stream
|
68
|
+
# * audio/mpeg
|
69
|
+
# * image/jpeg
|
70
|
+
# * text/plain
|
71
|
+
# * video/mpeg
|
71
72
|
#
|
72
73
|
def content_type= type
|
73
74
|
@content_type = type
|
@@ -88,11 +89,11 @@ module MIME
|
|
88
89
|
# +type+ is the disposition type of either "inline" or "attachment".
|
89
90
|
# +params+ is a Hash with zero or more of the following keys:
|
90
91
|
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
92
|
+
# +filename+ :: name of file
|
93
|
+
# +creation-date+ :: RFC2822 data-time
|
94
|
+
# +modification-date+ :: RFC2822 data-time
|
95
|
+
# +read-date+ :: RFC2822 data-time
|
96
|
+
# +size+ :: file size in octets
|
96
97
|
#
|
97
98
|
# The values for the *-date keys may use Time::rfc2822.
|
98
99
|
#
|
@@ -113,6 +114,5 @@ module MIME
|
|
113
114
|
end
|
114
115
|
|
115
116
|
end
|
116
|
-
|
117
117
|
end
|
118
118
|
end
|