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.
@@ -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 'rake/rdoctask'
1
+ require 'rdoc/task'
2
+ require 'rubygems/package_task'
2
3
  require 'rake/testtask'
3
- require 'rake/gempackagetask'
4
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|
5
+ Gem::PackageTask.new(eval File.read('mime.gemspec')) do |pkg|
20
6
  pkg.need_tar_gz = true
21
7
  end
22
8
 
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'
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
@@ -18,7 +18,7 @@
18
18
  #
19
19
  module MIME
20
20
 
21
- VERSION = '0.1'
21
+ VERSION = '0.2.0'
22
22
 
23
23
  end
24
24
 
@@ -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.match(/^(\w+)\//)[1]
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 :date, :from, :to
10
- attr_reader :cc, :bcc, :reply_to, :message_id
11
- attr_reader :comments, :keywords, :subject
12
-
13
-
14
- #
15
- # Required Headers
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)
@@ -6,13 +6,14 @@ module MIME
6
6
  #
7
7
  module MIME
8
8
 
9
- attr_reader :mime_version,
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
- # 1. 7bit
51
- # 2. 8bit
52
- # 3. binary
53
- # 4. quoted-printable
54
- # 5. base64
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
- # 1. application/octet-stream
67
- # 2. audio/mpeg
68
- # 3. image/jpeg
69
- # 4. text/plain
70
- # 5. video/mpeg
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
- # * filename => name of file
92
- # * creation-date => RFC2822 data-time
93
- # * modification-date => RFC2822 data-time
94
- # * read-date => RFC2822 data-time
95
- # * size => file size in octets
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