mime 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README +256 -0
  2. data/Rakefile +34 -0
  3. data/lib/mime.rb +32 -0
  4. data/lib/mime/composite_media_type.rb +169 -0
  5. data/lib/mime/content_types.rb +96 -0
  6. data/lib/mime/discrete_media_factory.rb +69 -0
  7. data/lib/mime/discrete_media_type.rb +79 -0
  8. data/lib/mime/error.rb +32 -0
  9. data/lib/mime/header_container.rb +34 -0
  10. data/lib/mime/headers/internet.rb +90 -0
  11. data/lib/mime/headers/mime.rb +118 -0
  12. data/lib/mime/media_type.rb +45 -0
  13. data/lib/mime/message.rb +51 -0
  14. data/lib/mime/parser.rb +16 -0
  15. data/test/mime_test.rb +386 -0
  16. data/test/scaffold/application.msg +8 -0
  17. data/test/scaffold/audio.msg +8 -0
  18. data/test/scaffold/book.pdf +0 -0
  19. data/test/scaffold/data.xml +17 -0
  20. data/test/scaffold/image.jpg +0 -0
  21. data/test/scaffold/image.msg +0 -0
  22. data/test/scaffold/index.html +6 -0
  23. data/test/scaffold/main.css +0 -0
  24. data/test/scaffold/mini.mov +0 -0
  25. data/test/scaffold/multipart_alternative.msg +17 -0
  26. data/test/scaffold/multipart_alternative_related.msg +0 -0
  27. data/test/scaffold/multipart_form_data_file.msg +0 -0
  28. data/test/scaffold/multipart_form_data_file_and_text.msg +0 -0
  29. data/test/scaffold/multipart_form_data_mixed.msg +0 -0
  30. data/test/scaffold/multipart_form_data_text.msg +40 -0
  31. data/test/scaffold/multipart_mixed_inline_and_attachment.msg +0 -0
  32. data/test/scaffold/multipart_mixed_inline_and_attachment2.msg +0 -0
  33. data/test/scaffold/multipart_related.msg +0 -0
  34. data/test/scaffold/plain_text_email.msg +9 -0
  35. data/test/scaffold/ruby.png +0 -0
  36. data/test/scaffold/song.mp3 +0 -0
  37. data/test/scaffold/text.msg +7 -0
  38. data/test/scaffold/unknown.yyy +1 -0
  39. data/test/scaffold/video.msg +8 -0
  40. 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.
@@ -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
@@ -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