mime 0.1 → 0.2.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 +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
|