1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
|
module MIME
#
# Composite media types allow encapsulating, mixing, and hierarchical
# structuring of entities of different types within a single message.
# Therefore, a CompositeMedia body is composed of one or more CompositeMedia
# and/or DiscreteMedia objects.
#
# CompositeMedia implements Content-Disposition for dictating presentation
# style of body entities via #add, #attach, and #inline. For more information
# on disposition parameters, such as filename, size, and modification-date,
# see https://tools.ietf.org/html/rfc2183.
#
# This class is abstract.
#
class CompositeMedia < Media
class Body
#
# Create new composite body.
#
def initialize boundary
@boundary = boundary
@body = Array.new
end
#
# Format the CompositeMedia object as a MIME message.
#
def to_s
all_entities = @body.join("\r\n--#{@boundary}\r\n")
"--#{@boundary}\r\n#{all_entities}\r\n--#{@boundary}--\r\n"
end
#
# Add +entity+ to the composite body.
#
def add entity
@body.push(entity)
end
end
attr_reader :boundary
def initialize content_type
AbstractClassError.no_instantiation(self, CompositeMedia)
@boundary = "Boundary_#{ID.generate_id}" # delimits body entities
super(Body.new(boundary), content_type, 'boundary' => boundary)
end
#
# Add a Media +entity+ to the message.
#
# The entity will be added to the main body of the message with no
# disposition specified. Presentation of the entity will be dictated by
# the display user agent.
#
# === Text and HTML Multipart/Alternative message
#
# A display user agent may only be capable of displaying plain text. If so,
# it will choose to display the Text/Plain entity. However, if it is capable
# of displaying HTML, it may choose to display the Text/HTML version.
#
# msg = MIME::Multipart::Alternative.new
# msg.add(MIME::Text.new('plain text'))
# msg.add(MIME::Text.new('<html>html text</html>', 'html'))
#
# The order in which the entities are added is significant. Add the simplest
# representations first or in increasing order of complexity.
#
def add entity
raise Error.new('can only add Media objects') unless entity.is_a? Media
@body.add(entity)
end
#
# Attach a Media +entity+ to the message.
#
# The entity will be presented as separate from the main body of the
# message. Thus, display of the entity will not be automatic, but contingent
# upon some further action of the user. For example, the display user agent
# may present an icon representation of the entity, which the user can
# select to view or save the entity.
#
# === Attachment with filename and size parameters:
#
# f = File.open('file.txt')
# file = MIME::Text.new(f.read)
# text = MIME::Text.new('See the attached file.')
#
# msg = MIME::Multipart::Mixed.new
# msg.inline(text)
# msg.attach(file, 'filename' => f.path, 'size' => f.size)
#
def attach entity, params = {}
entity.set_disposition('attachment', params)
add(entity)
end
#
# Inline a Media +entity+ in the message.
#
# The entity will be embedded within the main body of the message. Thus,
# display of the entity will be automatic upon display of the message.
# Inline entities should be added in the order in which they occur within
# the message.
#
# === Message with two embedded images:
#
# msg = MIME::Multipart::Mixed.new
# msg.inline(MIME::Image.new(File.read('screenshot1.png'), 'png'))
# msg.inline(MIME::Image.new(File.read('screenshot2.png'), 'png'))
# msg.description = 'My screenshots'
#
def inline entity, params = {}
entity.set_disposition('inline', params)
add(entity)
end
end
#
# Message is intended to encapsulate another message. In particular, the
# <em>message/rfc822</em> content type is used to encapsulate RFC 822
# messages.
#
# TODO Implement
#
class Message < CompositeMedia
end
#
# The abstract base class for all multipart message subtypes. The entities of
# a multipart message are delimited by a unique boundary.
#
class Multipart < CompositeMedia
def initialize media_subtype
AbstractClassError.no_instantiation(self, Multipart)
super("multipart/#{media_subtype}")
end
end
#
# The Alternative subtype indicates that each contained entity is an
# alternatively formatted version of the same content.
#
# In general, when composing multipart/alternative entities, add the body
# parts in increasing order of preference; that is, with the preferred format
# last. For example, to prioritize for fancy text, add the plainest format
# first and the richest format last. Typically, receiving user agents select
# the last format they are capable of displaying or interpreting.
#
class Multipart::Alternative < Multipart
#
# Returns a Multipart::Alternative object with a content type of
# multipart/alternative.
#
def initialize
super('alternative')
end
end
#
# The FormData subtype expresses values for HTML form data submissions.
# ---
# RFCs consulted during implementation:
#
# * RFC-1867 Form-based File Upload in HTML
# * RFC-2388 Returning Values from Forms: multipart/form-data
#
class Multipart::FormData < Multipart
#
# Returns a Multipart::FormData object with a content type of
# multipart/form-data.
#
def initialize
super('form-data')
end
#
# Add the Media object, +entity+, to the FormData object. +name+ is
# typically an HTML input tag variable name. If the input tag is of type
# _file_, then +filename+ must be specified to indicate a file upload.
#
def add entity, name, filename = nil
entity.set_disposition('form-data', 'name' => name, 'filename' => filename)
super(entity)
end
end
#
# The Mixed subtype aggregates contextually independent entities.
#
class Multipart::Mixed < Multipart
#
# Returns a Multipart::Mixed object with a content type of
# multipart/mixed.
#
def initialize
super('mixed')
end
end
#
# The Related subtype aggregates multiple related entities. The message
# consists of a root (the first entity) which references subsequent inline
# entities. Message entities should be referenced by their Content-ID header.
# The syntax of a reference is unspecified and is instead dictated by the
# encoding or protocol used in the entity.
# ---
# RFC consulted during implementation:
#
# * RFC-2387 The MIME Multipart/Related Content-type
#
class Multipart::Related < Multipart
#
# Returns a Multipart::Related object with a content type of
# multipart/related.
#
def initialize
super('related')
end
#
# The first entity added becomes the root object. The related message
# type is set to the value of the root object media type.
#--
# The "type" parameter is required and should equal the root media type.
# https://tools.ietf.org/html/rfc2387#section-3.1
#
def add entity
unless type.include? '; type='
root_type = entity.type.partition(';').first # omit parameters
self.type = append_field_params(type, 'type' => root_type)
end
super
end
end
end
|