mime 0.1

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.
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