mime 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +192 -140
- data/lib/mime/composite_media.rb +230 -0
- data/lib/mime/content_formats/text_flowed.rb +6 -6
- data/lib/mime/discrete_media.rb +73 -0
- data/lib/mime/discrete_media_factory.rb +19 -13
- data/lib/mime/header.rb +48 -0
- data/lib/mime/headers/internet.rb +124 -27
- data/lib/mime/headers/mime.rb +38 -27
- data/lib/mime/mail.rb +51 -0
- data/lib/mime/media.rb +30 -0
- data/lib/mime/parser.rb +1 -1
- data/lib/mime.rb +27 -9
- data/test/scaffold/application.msg +2 -5
- data/test/scaffold/audio.msg +2 -5
- data/test/scaffold/image.msg +0 -0
- data/test/scaffold/multipart_alternative.msg +7 -7
- 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 +12 -12
- 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/rfc822_composite.msg +0 -0
- data/test/scaffold/{plain_text_email.msg → rfc822_discrete.msg} +5 -5
- data/test/scaffold/text.msg +2 -5
- data/test/scaffold/video.msg +2 -5
- data/test/test_mime.rb +323 -150
- data/test/test_text_flowed.rb +1 -1
- metadata +13 -12
- data/lib/mime/composite_media_type.rb +0 -169
- data/lib/mime/discrete_media_type.rb +0 -78
- data/lib/mime/header_container.rb +0 -32
- data/lib/mime/media_type.rb +0 -51
- data/lib/mime/message.rb +0 -61
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-04-18 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! 'A library for building RFC compliant Multipurpose Internet Mail Extensions
|
15
15
|
|
@@ -32,14 +32,14 @@ files:
|
|
32
32
|
- lib/mime/headers/internet.rb
|
33
33
|
- lib/mime/discrete_media_factory.rb
|
34
34
|
- lib/mime/error.rb
|
35
|
-
- lib/mime/header_container.rb
|
36
35
|
- lib/mime/parser.rb
|
37
36
|
- lib/mime/content_types.rb
|
38
|
-
- lib/mime/
|
39
|
-
- lib/mime/
|
40
|
-
- lib/mime/discrete_media_type.rb
|
41
|
-
- lib/mime/message.rb
|
37
|
+
- lib/mime/media.rb
|
38
|
+
- lib/mime/composite_media.rb
|
42
39
|
- lib/mime/content_formats/text_flowed.rb
|
40
|
+
- lib/mime/header.rb
|
41
|
+
- lib/mime/mail.rb
|
42
|
+
- lib/mime/discrete_media.rb
|
43
43
|
- lib/mime.rb
|
44
44
|
- test/scaffold/application.msg
|
45
45
|
- test/scaffold/unknown.yyy
|
@@ -47,23 +47,24 @@ files:
|
|
47
47
|
- test/scaffold/mini.mov
|
48
48
|
- test/scaffold/image.jpg
|
49
49
|
- test/scaffold/book.pdf
|
50
|
-
- test/scaffold/plain_text_email.msg
|
51
50
|
- test/scaffold/song.mp3
|
52
51
|
- test/scaffold/ruby.png
|
53
52
|
- test/scaffold/multipart_alternative.msg
|
54
|
-
- test/scaffold/multipart_alternative_related.msg
|
55
|
-
- test/scaffold/multipart_related.msg
|
56
53
|
- test/scaffold/multipart_mixed_inline_and_attachment2.msg
|
57
54
|
- test/scaffold/image.msg
|
58
55
|
- test/scaffold/multipart_form_data_file_and_text.msg
|
59
56
|
- test/scaffold/data.xml
|
60
|
-
- test/scaffold/
|
57
|
+
- test/scaffold/rfc822_composite.msg
|
61
58
|
- test/scaffold/text.msg
|
62
59
|
- test/scaffold/main.css
|
63
|
-
- test/scaffold/
|
60
|
+
- test/scaffold/multipart_alternative_related.msg
|
64
61
|
- test/scaffold/multipart_form_data_text.msg
|
65
62
|
- test/scaffold/audio.msg
|
63
|
+
- test/scaffold/multipart_related.msg
|
64
|
+
- test/scaffold/rfc822_discrete.msg
|
66
65
|
- test/scaffold/data.htm
|
66
|
+
- test/scaffold/multipart_form_data_mixed.msg
|
67
|
+
- test/scaffold/multipart_mixed_inline_and_attachment.msg
|
67
68
|
- test/test_mime.rb-try
|
68
69
|
- test/test_mime.rb
|
69
70
|
- test/test_text_flowed.rb
|
@@ -1,169 +0,0 @@
|
|
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
|
@@ -1,78 +0,0 @@
|
|
1
|
-
module MIME
|
2
|
-
|
3
|
-
#
|
4
|
-
# Discrete media types must be handled by non-MIME mechanisms; they are
|
5
|
-
# opaque to MIME processors. Therefore, the body of a DiscreteMediaType
|
6
|
-
# object does not need further MIME processing.
|
7
|
-
#
|
8
|
-
# This class is abstract.
|
9
|
-
#
|
10
|
-
class DiscreteMediaType < MediaType
|
11
|
-
def initialize(body, media_subtype, content_params)
|
12
|
-
AbstractClassError.no_instantiation(self, DiscreteMediaType)
|
13
|
-
super(body, "#{@media_type}/#{media_subtype}", content_params)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
#
|
18
|
-
# ApplicationMedia is intended for discrete data that is to be processed by
|
19
|
-
# some type of application program. The body contains information which must
|
20
|
-
# be processed by an application before it is viewable or usable by a user.
|
21
|
-
#
|
22
|
-
# ApplicationMedia is the catch all class. If your content cannot be
|
23
|
-
# identified as another DiscreteMediaType, then it is application media.
|
24
|
-
#
|
25
|
-
class ApplicationMedia < DiscreteMediaType
|
26
|
-
def initialize(body, subtype = 'octet-stream', params = {})
|
27
|
-
@media_type = 'application'
|
28
|
-
super
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
#
|
33
|
-
# AudioMedia is intended for discrete audio content. The +subtype+ indicates
|
34
|
-
# the specific audio format, such as *mpeg* or *midi*.
|
35
|
-
#
|
36
|
-
class AudioMedia < DiscreteMediaType
|
37
|
-
def initialize(body, subtype = 'basic', params = {})
|
38
|
-
@media_type = 'audio'
|
39
|
-
super
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
#
|
44
|
-
# ImageMedia is intented for discrete image content. The +subtype+ indicates
|
45
|
-
# the specific image format, such as *jpeg* or *gif*.
|
46
|
-
#
|
47
|
-
class ImageMedia < DiscreteMediaType
|
48
|
-
def initialize(body, subtype = 'jpeg', params = {})
|
49
|
-
@media_type = 'image'
|
50
|
-
super
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
#
|
55
|
-
# TextMedia is intended for content which is principally textual in form. The
|
56
|
-
# +subtype+ indicates the specific text type, such as *plain* or *html*.
|
57
|
-
#
|
58
|
-
class TextMedia < DiscreteMediaType
|
59
|
-
def initialize(body, subtype = 'plain', params = {})
|
60
|
-
@media_type = 'text'
|
61
|
-
super
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
#
|
66
|
-
# VideoMedia is intended for discrete video content. The content +subtype+
|
67
|
-
# indicates the specific video format. The RFC describes video media as
|
68
|
-
# content that contains a time-varying-picture image, possibly with color and
|
69
|
-
# coordinated sound.
|
70
|
-
#
|
71
|
-
class VideoMedia < DiscreteMediaType
|
72
|
-
def initialize(body, subtype = 'mpeg', params = {})
|
73
|
-
@media_type = 'video'
|
74
|
-
super
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module MIME
|
2
|
-
|
3
|
-
#
|
4
|
-
# Storage for RFC 2822 headers.
|
5
|
-
#
|
6
|
-
class HeaderContainer
|
7
|
-
|
8
|
-
#
|
9
|
-
# Return a new header container object.
|
10
|
-
#
|
11
|
-
def initialize
|
12
|
-
@headers = Hash.new
|
13
|
-
end
|
14
|
-
|
15
|
-
#
|
16
|
-
# Convert all headers to their string equivalents and join them using the
|
17
|
-
# RFC 2822 CRLF line separator.
|
18
|
-
#
|
19
|
-
def to_s
|
20
|
-
@headers.to_a.map {|kv| kv.join(": ")}.join("\r\n")
|
21
|
-
end
|
22
|
-
|
23
|
-
#
|
24
|
-
# Add the +name+/+value+ pair to the header container.
|
25
|
-
#
|
26
|
-
def add name, value
|
27
|
-
@headers.store(name, value)
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
data/lib/mime/media_type.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'mime/error'
|
2
|
-
require 'mime/header_container'
|
3
|
-
require 'mime/headers/mime'
|
4
|
-
|
5
|
-
module MIME
|
6
|
-
|
7
|
-
#
|
8
|
-
# Abstract top-level media type class.
|
9
|
-
#
|
10
|
-
class MediaType
|
11
|
-
|
12
|
-
include Headers::MIME
|
13
|
-
|
14
|
-
attr_reader :headers
|
15
|
-
attr_accessor :body
|
16
|
-
protected :body, :body=
|
17
|
-
|
18
|
-
def initialize body, content_type, content_params = {}
|
19
|
-
AbstractClassError.no_instantiation(self, MediaType)
|
20
|
-
|
21
|
-
@headers = HeaderContainer.new
|
22
|
-
@body = body
|
23
|
-
self.content_id = unique_id
|
24
|
-
self.content_type =
|
25
|
-
if content_params.empty?
|
26
|
-
content_type
|
27
|
-
else
|
28
|
-
params = content_params.to_a.map {|kv| kv.join('=')}.join('; ')
|
29
|
-
"#{content_type}; #{params}"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
#
|
34
|
-
# Transform the the MediaType object into a MIME message.
|
35
|
-
#
|
36
|
-
def to_s
|
37
|
-
"#{headers}\r\n\r\n#{body}"
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
#
|
43
|
-
# Generate a globally unique identifier for use in boundaries and IDs.
|
44
|
-
#
|
45
|
-
def unique_id
|
46
|
-
"#{object_id.abs}#{rand}"
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
data/lib/mime/message.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'time'
|
2
|
-
require 'mime/headers/internet'
|
3
|
-
require 'mime/headers/mime'
|
4
|
-
|
5
|
-
|
6
|
-
module MIME
|
7
|
-
|
8
|
-
#
|
9
|
-
# Construct textual messages using the RFC 2822 Internet message format.
|
10
|
-
#
|
11
|
-
class Message
|
12
|
-
|
13
|
-
include Headers::Internet
|
14
|
-
include Headers::MIME
|
15
|
-
|
16
|
-
# HeaderContainer access
|
17
|
-
attr_reader :headers
|
18
|
-
|
19
|
-
attr_accessor :body
|
20
|
-
|
21
|
-
#
|
22
|
-
# Return a Message object with body optionally set to +body+.
|
23
|
-
#
|
24
|
-
def initialize body = nil
|
25
|
-
@body = body
|
26
|
-
@headers = HeaderContainer.new
|
27
|
-
self.date = Time.now.rfc2822
|
28
|
-
self.message_id = "#{rand(1E9)}@#{__id__.abs}"
|
29
|
-
self.mime_version = "1.0 (Ruby MIME v#{VERSION})"
|
30
|
-
end
|
31
|
-
|
32
|
-
#
|
33
|
-
# Return the Internet message formatted representation of the instance.
|
34
|
-
#
|
35
|
-
def to_s
|
36
|
-
#--
|
37
|
-
# In an RFC 2822 message, the header and body sections must be separated
|
38
|
-
# by two line breaks (CRLF). One line break is deliberately missing,
|
39
|
-
# allowing a body supplier to append headers to the top-level message
|
40
|
-
# header section. Consequently, the body supplier is responsible for
|
41
|
-
# handling the body-header separation. Furthermore, if the +body+ is
|
42
|
-
# empty, the header section will be properly terminated, creating a
|
43
|
-
# standards compliant message.
|
44
|
-
#++
|
45
|
-
|
46
|
-
# FIXME writing directly to body VS setting body with a MediaType gives an
|
47
|
-
# imbalance of "\r\n". Setting body directly with string requires a CRLF
|
48
|
-
# prepended, but not the case for TextMedia for instance. So where do we
|
49
|
-
# handle this? Perhaps HeaderContainer#to_s should append a CRLF, or
|
50
|
-
# self#body should detect String and automatically prepend CRLF??? Then we
|
51
|
-
# remove the CRLF here between the headers and body.
|
52
|
-
#
|
53
|
-
# Also, TextMedia adds the Content-ID and Content-Type headers, which we
|
54
|
-
# don't get if body is set directly with String. Does this matter?
|
55
|
-
|
56
|
-
"#{headers}\r\n#{body}\r\n"
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|