mime 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +256 -0
- data/Rakefile +34 -0
- data/lib/mime.rb +32 -0
- data/lib/mime/composite_media_type.rb +169 -0
- data/lib/mime/content_types.rb +96 -0
- data/lib/mime/discrete_media_factory.rb +69 -0
- data/lib/mime/discrete_media_type.rb +79 -0
- data/lib/mime/error.rb +32 -0
- data/lib/mime/header_container.rb +34 -0
- data/lib/mime/headers/internet.rb +90 -0
- data/lib/mime/headers/mime.rb +118 -0
- data/lib/mime/media_type.rb +45 -0
- data/lib/mime/message.rb +51 -0
- data/lib/mime/parser.rb +16 -0
- data/test/mime_test.rb +386 -0
- data/test/scaffold/application.msg +8 -0
- data/test/scaffold/audio.msg +8 -0
- data/test/scaffold/book.pdf +0 -0
- data/test/scaffold/data.xml +17 -0
- data/test/scaffold/image.jpg +0 -0
- data/test/scaffold/image.msg +0 -0
- data/test/scaffold/index.html +6 -0
- data/test/scaffold/main.css +0 -0
- data/test/scaffold/mini.mov +0 -0
- data/test/scaffold/multipart_alternative.msg +17 -0
- data/test/scaffold/multipart_alternative_related.msg +0 -0
- data/test/scaffold/multipart_form_data_file.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 +40 -0
- 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 -0
- data/test/scaffold/ruby.png +0 -0
- data/test/scaffold/song.mp3 +0 -0
- data/test/scaffold/text.msg +7 -0
- data/test/scaffold/unknown.yyy +1 -0
- data/test/scaffold/video.msg +8 -0
- metadata +92 -0
data/README
ADDED
@@ -0,0 +1,256 @@
|
|
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
|
+
+----------------+ | (N) |
|
71
|
+
| Body | | |---RFC822 Message
|
72
|
+
+----------------+ | |
|
73
|
+
---+ |
|
74
|
+
---+ |
|
75
|
+
+----------------+ | |
|
76
|
+
| MIME Headers | | |
|
77
|
+
+----------------+ |---MIME Entity |
|
78
|
+
+----------------+ | (N+1) |
|
79
|
+
| Body | | |
|
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,
|
86
|
+
composite entity bodies may contain other composite or discrete entites and so
|
87
|
+
on. However, discrete entities are non-recursive and contain only non-MIME
|
88
|
+
bodies.
|
89
|
+
|
90
|
+
|
91
|
+
== Examples
|
92
|
+
|
93
|
+
<em>The following examples imply that the MIME module is included.</em>
|
94
|
+
|
95
|
+
|
96
|
+
=== Two ways to instantiate a DiscreteMediaType object using a file path
|
97
|
+
|
98
|
+
fpath = '/tmp/data.xml'
|
99
|
+
text_media = open(fpath) {|f| TextMedia.new(f.read, 'text/xml')}
|
100
|
+
text_media = DiscreteMediaFactory.create(fpath)
|
101
|
+
|
102
|
+
|
103
|
+
=== Simple text/plain RFC822 email
|
104
|
+
|
105
|
+
msg = Message.new # creates a blank message with date and message ID headers
|
106
|
+
msg.date = (Time.now - 3600).rfc2822 # specify a different date
|
107
|
+
msg.subject = 'This is important'
|
108
|
+
msg.headers.add('X-Priority', 'high') # custom header
|
109
|
+
|
110
|
+
msg.body = TextMedia.new('hello, it is me!')
|
111
|
+
#
|
112
|
+
# The following snippets are equivalent to the previous line.
|
113
|
+
#
|
114
|
+
# msg.body = "\r\nhello, it is me!"
|
115
|
+
# msg.header.add('Content-Type', 'text/plain; charset=us-ascii')
|
116
|
+
#
|
117
|
+
# --OR--
|
118
|
+
#
|
119
|
+
# msg.body = "Content-Type: text/plain; charset=us-ascii\r\n\r\nhello, it is me!"
|
120
|
+
|
121
|
+
msg.to = {
|
122
|
+
's13xj@x.com' => nil, # no name display
|
123
|
+
'james@x.com' => 'James',
|
124
|
+
'clint@x.com' => 'Clint',
|
125
|
+
}
|
126
|
+
msg.from = {
|
127
|
+
'theboss@x.com' => 'Boss Man'
|
128
|
+
}
|
129
|
+
|
130
|
+
msg.to_s # ready to be sent via SMTP
|
131
|
+
|
132
|
+
|
133
|
+
=== Plain text multipart/mixed message with a file attachment
|
134
|
+
|
135
|
+
The multipart/mixed content type can be used to aggregate multiple unrelated
|
136
|
+
entities.
|
137
|
+
|
138
|
+
text = DiscreteMediaFactory.create('/tmp/data.txt')
|
139
|
+
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
140
|
+
|
141
|
+
mixed_msg = MultipartMedia::Mixed.new
|
142
|
+
mixed_msg.attach_entity(image)
|
143
|
+
mixed_msg.add_entity(text)
|
144
|
+
mixed_msg.to_s
|
145
|
+
|
146
|
+
|
147
|
+
=== Plain text and HTML multipart/alternative MIME message
|
148
|
+
|
149
|
+
The multipart/alternative content type allows for multiple alternatively
|
150
|
+
formatted versions of the same content. Clients are then responsible for
|
151
|
+
choosing the most suitable version for display.
|
152
|
+
|
153
|
+
text_msg = TextMedia.new(<<-text_data, 'text/plain')
|
154
|
+
*Headline*
|
155
|
+
Ruby is cool!
|
156
|
+
text_data
|
157
|
+
|
158
|
+
html_msg = TextMedia.new(<<-html_data, 'text/html')
|
159
|
+
<html>
|
160
|
+
<body>
|
161
|
+
<h1>Headline</h1>
|
162
|
+
<p>Ruby is cool!</p>
|
163
|
+
</body>
|
164
|
+
</html>
|
165
|
+
html_data
|
166
|
+
|
167
|
+
msg = MultipartMedia::Alternative.new
|
168
|
+
msg.add_entity(html_msg) # most complex representation must be added first
|
169
|
+
msg.add_entity(text_msg)
|
170
|
+
msg.to_s
|
171
|
+
|
172
|
+
|
173
|
+
=== HTML multipart/related MIME email with embedded image
|
174
|
+
|
175
|
+
Sometimes it is desirable to send a document that is made up of many separate
|
176
|
+
parts. For example, an HTML page with embedded images. The multipart/related
|
177
|
+
content type aggregates all the parts and creates the means for the root entity
|
178
|
+
to reference the other entities.
|
179
|
+
|
180
|
+
image = DiscreteMediaFactory.create('/tmp/ruby.png')
|
181
|
+
image.content_transfer_encoding = 'binary'
|
182
|
+
|
183
|
+
html_msg = TextMedia.new(<<-html_data, 'text/html; charset=iso-8859-1')
|
184
|
+
<html>
|
185
|
+
<body>
|
186
|
+
<h1>Ruby Image</h1>
|
187
|
+
<p>Check out this cool pic.</p>
|
188
|
+
<img alt="cool ruby" src="cid:#{image.content_id}"/>
|
189
|
+
<p>Wasn't it cool?</p>
|
190
|
+
</body>
|
191
|
+
</html>
|
192
|
+
html_data
|
193
|
+
html_msg.content_transfer_encoding = '7bit'
|
194
|
+
|
195
|
+
related_msg = MultipartMedia::Related.new
|
196
|
+
related_msg.inline_entity(image)
|
197
|
+
related_msg.add_entity(html_msg)
|
198
|
+
|
199
|
+
email_msg = Message.new(related_msg)
|
200
|
+
email_msg.to = {'joe@domain.com' => 'Joe Schmo'}
|
201
|
+
email_msg.from = {'john@domain.com' => 'John Doe'}
|
202
|
+
email_msg.subject = 'Ruby is cool'
|
203
|
+
email_msg.to_s
|
204
|
+
|
205
|
+
|
206
|
+
=== HTML form with file upload using multipart/form-data encoding
|
207
|
+
|
208
|
+
This example builds a representation of an HTML form that can be POSTed to an
|
209
|
+
HTTP server. It contains a single text input and a file input.
|
210
|
+
|
211
|
+
name_field = TextMedia.new('Joe Blow')
|
212
|
+
|
213
|
+
portrait_filename = '/tmp/joe_portrait.jpg'
|
214
|
+
portrait_field = open(portrait_filename) do |f|
|
215
|
+
ImageMedia.new(f.read, 'image/jpeg') # explicit content type
|
216
|
+
end
|
217
|
+
portrait_field.content_transfer_encoding = 'binary'
|
218
|
+
|
219
|
+
form_data = MultipartMedia::FormData.new
|
220
|
+
form_data.add_entity(name_field, 'name')
|
221
|
+
form_data.add_entity(portrait_field, 'portrait', portrait_filename) # explicity filename
|
222
|
+
form_data.to_s
|
223
|
+
|
224
|
+
|
225
|
+
=== HTML form with file upload using multipart/form-data encoding (DiscreteMediaFactory)
|
226
|
+
|
227
|
+
The outcome of this example is identical to the previous example. The only
|
228
|
+
semantic difference is that the MIME::DiscreteMediaFactory class is used to
|
229
|
+
automatically instantiate the MIME::MediaType object.
|
230
|
+
|
231
|
+
name_field = TextMedia.new('Joe Blow')
|
232
|
+
|
233
|
+
portrait_field = DiscreteMediaFactory.create('/tmp/joe_portrait.jpg') # no explicit content type
|
234
|
+
portrait_field.content_transfer_encoding = 'binary'
|
235
|
+
|
236
|
+
form_data = MultipartMedia::FormData.new
|
237
|
+
form_data.add_entity(name_field, 'name')
|
238
|
+
form_data.add_entity(portrait_field, 'portrait') # no explicit filename
|
239
|
+
form_data.to_s
|
240
|
+
|
241
|
+
|
242
|
+
== More Examples
|
243
|
+
|
244
|
+
For many more examples, check the test class MIMETest.
|
245
|
+
|
246
|
+
|
247
|
+
== Contact
|
248
|
+
|
249
|
+
Please email inquiries to pachl at ecentryx dot com.
|
250
|
+
|
251
|
+
[Home Page] http://mime.rubyforge.org/
|
252
|
+
[RubyForge] http://rubyforge.org/projects/mime/
|
253
|
+
|
254
|
+
== License
|
255
|
+
|
256
|
+
The entire MIME library is free to use under the terms of the Ruby license.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
gem_spec = Gem::Specification.new do |s|
|
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|
|
20
|
+
pkg.need_tar_gz = true
|
21
|
+
end
|
22
|
+
|
23
|
+
Rake::RDocTask.new do |rd|
|
24
|
+
rd.rdoc_files.include('README', 'lib/')
|
25
|
+
rd.rdoc_dir = 'doc'
|
26
|
+
rd.main = 'README'
|
27
|
+
rd.options << '--all' << '--inline-source'
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::TestTask.new do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.pattern = 'test/*_test.rb'
|
33
|
+
t.warning = true
|
34
|
+
end
|
data/lib/mime.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# = Construct Multipurpose Internet Mail Extensions (MIME) messages.
|
3
|
+
#
|
4
|
+
# ---
|
5
|
+
#
|
6
|
+
# RFCs referenced during the implementation of this library:
|
7
|
+
#
|
8
|
+
# * RFC-2822 Internet Message Format (obsoletes 822)
|
9
|
+
# * RFC-2045 MIME Part 1: Format of Internet Message Bodies
|
10
|
+
# * RFC-2046 MIME Part 2: Media Types
|
11
|
+
# * RFC-2047 MIME Part 3: Message Header Extensions for Non-ASCII Text
|
12
|
+
# * RFC-2048 MIME Part 4: Registration Procedures
|
13
|
+
# * RFC-2049 MIME Part 5: Conformance Criteria and Examples
|
14
|
+
#
|
15
|
+
# ---
|
16
|
+
#
|
17
|
+
# See SOAP::MIMEMessage for other implementation ideas.
|
18
|
+
#
|
19
|
+
module MIME
|
20
|
+
|
21
|
+
VERSION = '0.1'
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'mime/content_types'
|
26
|
+
require 'mime/error'
|
27
|
+
require 'mime/header_container'
|
28
|
+
require 'mime/media_type'
|
29
|
+
require 'mime/discrete_media_type'
|
30
|
+
require 'mime/composite_media_type'
|
31
|
+
require 'mime/message'
|
32
|
+
require 'mime/discrete_media_factory'
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module MIME
|
2
|
+
|
3
|
+
#
|
4
|
+
# Composite entities are handled using MIME mechanisms. A MIME processor must
|
5
|
+
# handle the body directly. A CompositeMediaType object is composed of one or
|
6
|
+
# more CompositeMediaType and/or DiscreteMediaType objects.
|
7
|
+
#
|
8
|
+
# This class is abstract.
|
9
|
+
#
|
10
|
+
class CompositeMediaType < MediaType
|
11
|
+
|
12
|
+
def initialize content_type
|
13
|
+
AbstractClassError.no_instantiation(self, CompositeMediaType)
|
14
|
+
|
15
|
+
super(nil, content_type)
|
16
|
+
@entities = Array.new
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Add a MediaType object to the message.
|
21
|
+
#
|
22
|
+
def add_entity entity
|
23
|
+
raise Error.new('can only add MediaType objects') unless entity.is_a? MediaType
|
24
|
+
@entities.unshift(entity)
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Attach a MediaType object to the message.
|
29
|
+
#
|
30
|
+
def attach_entity entity, params = {}
|
31
|
+
entity.set_content_disposition('attachment', params)
|
32
|
+
add_entity(entity)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Inline a MediaType object in the message.
|
37
|
+
#
|
38
|
+
def inline_entity entity, params = {}
|
39
|
+
entity.set_content_disposition('inline', params)
|
40
|
+
add_entity(entity)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# MessageMedia is intended to encapsulate another message. In particular,
|
47
|
+
# the <em>message/rfc822</em> content type is used to encapsulate RFC 822
|
48
|
+
# messages.
|
49
|
+
#
|
50
|
+
# TODO Implement
|
51
|
+
#
|
52
|
+
class MessageMedia < CompositeMediaType
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# The abstract base class for all multipart message subtypes. The entities of
|
57
|
+
# a multipart message are delimited by a unique boundary.
|
58
|
+
#
|
59
|
+
class MultipartMedia < CompositeMediaType
|
60
|
+
|
61
|
+
def initialize content_type
|
62
|
+
AbstractClassError.no_instantiation(self, MultipartMedia)
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# The boundary used to separate the message entities.
|
68
|
+
#
|
69
|
+
def boundary
|
70
|
+
@boundary ||= "Boundary_#{unique_id}"
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Return the multipart representation of the body.
|
75
|
+
#
|
76
|
+
def body
|
77
|
+
all_entities = @entities.join("\r\n--#{boundary}\r\n")
|
78
|
+
"--#{boundary}\r\n#{all_entities}\r\n--#{boundary}--\r\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# The Alternative subtype indicates that each contained entity is an
|
85
|
+
# alternatively formatted version of the same content. The most complex
|
86
|
+
# version should be added to the message first, i.e. it will be sequentially
|
87
|
+
# last in the message.
|
88
|
+
#
|
89
|
+
class MultipartMedia::Alternative < MultipartMedia
|
90
|
+
|
91
|
+
#
|
92
|
+
# Returns a MultipartMedia::Alternative object with a content type of
|
93
|
+
# multipart/alternative.
|
94
|
+
#
|
95
|
+
def initialize
|
96
|
+
super("multipart/alternative; boundary=#{boundary}")
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# The FormData subtype expresses values for HTML form data submissions.
|
103
|
+
# ---
|
104
|
+
# RFCs consulted during implementation:
|
105
|
+
#
|
106
|
+
# * RFC-1867 Form-based File Upload in HTML
|
107
|
+
# * RFC-2388 Returning Values from Forms: multipart/form-data
|
108
|
+
#
|
109
|
+
class MultipartMedia::FormData < MultipartMedia
|
110
|
+
|
111
|
+
#
|
112
|
+
# Returns a MultipartMedia::FormData object with a content type of
|
113
|
+
# multipart/form-data.
|
114
|
+
#
|
115
|
+
def initialize
|
116
|
+
super("multipart/form-data; boundary=#{boundary}")
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Add the MediaType object, +entity+, to the FormData object. +name+ is
|
121
|
+
# typically an HTML input tag variable name. If the input tag is of type
|
122
|
+
# _file_, then +filename+ must be specified to indicate a file upload.
|
123
|
+
#
|
124
|
+
def add_entity entity, name, filename = nil
|
125
|
+
entity.set_content_disposition('form-data', 'name' => name, 'filename' => filename)
|
126
|
+
super(entity)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# The Mixed subtype aggregates contextually independent entities.
|
133
|
+
#
|
134
|
+
class MultipartMedia::Mixed < MultipartMedia
|
135
|
+
|
136
|
+
#
|
137
|
+
# Returns a MultipartMedia::Mixed object with a content type of
|
138
|
+
# multipart/mixed.
|
139
|
+
#
|
140
|
+
def initialize
|
141
|
+
super("multipart/mixed; boundary=#{boundary}")
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# The Related subtype aggregates multiple related entities. The message
|
148
|
+
# consists of a root (the first entity) which references subsequent inline
|
149
|
+
# entities. Message entities should be referenced by their Content-ID header.
|
150
|
+
# The syntax of a reference is unspecified and is instead dictated by the
|
151
|
+
# encoding or protocol used in the entity.
|
152
|
+
# ---
|
153
|
+
# RFC consulted during implementation:
|
154
|
+
#
|
155
|
+
# * RFC-2387 The MIME Multipart/Related Content-type
|
156
|
+
#
|
157
|
+
class MultipartMedia::Related < MultipartMedia
|
158
|
+
|
159
|
+
#
|
160
|
+
# Returns a MultipartMedia::Related object with a content type of
|
161
|
+
# multipart/related.
|
162
|
+
#
|
163
|
+
def initialize
|
164
|
+
super("multipart/related; boundary=#{boundary}")
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|