#--
# Copyright (C) 2002, 2003 Matt Armstrong. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
# NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#++
# Implements the RMail::Serialize class.
module RMail
# The RMail::Serialize class writes an RMail::Message object into an
# IO object or string. The result is a standard mail message in
# text form.
#
# To do this, you pass the RMail::Message object to the
# RMail::Serialize object. RMail::Serialize can write into any
# object supporting the << method.
#
# As a convenience, RMail::Serialize.write is a class method you can
# use directly:
#
# # Write to a file
# File.open('my-message', 'w') { |f|
# RMail::Serialize.write(f, message)
# }
#
# # Write to a new string
# string = RMail::Serialize.write('', message)
class Serialize
@@boundary_count = 0
# Initialize this Serialize object with an output stream. If
# escape_from is not nil, lines with a leading From are escaped.
def initialize(output, escape_from = nil)
@output = output
@escape_from = escape_from
end
# Serialize a given message into this object's output object.
def serialize(message)
calculate_boundaries(message) if message.multipart?
serialize_low(message)
end
# Serialize a message into a given output object. The output
# object must support the << method in the same way that an IO or
# String object does.
def Serialize.write(output, message)
Serialize.new(output).serialize(message)
end
private
def serialize_low(message, depth = 0)
if message.multipart?
delimiters, delimiters_boundary = message.get_delimiters
unless delimiters
boundary = "\n--" + message.header.param('Content-Type', 'boundary')
delimiters = Array.new(message.body.length + 1, boundary + "\n")
delimiters[-1] = boundary + "--\n"
end
@output << message.header.to_s
if message.body.length > 0 or message.preamble or
delimiters.last.length > 0
@output << "\n"
end
if message.preamble
@output << message.preamble
end
delimiter = 0
message.each_part { |part|
@output << delimiters[delimiter]
delimiter = delimiter.succ
serialize_low(part, depth + 1)
}
@output << delimiters[delimiter]
if message.epilogue
@output << message.epilogue
end
else
@output << message.header.to_s
unless message.body.nil?
@output << "\n"
@output << message.body
if depth == 0 and message.body.length > 0 and
message.body[-1] != ?\n
@output << "\n"
end
end
end
@output
end
# Walk the multipart tree and make sure the boundaries generated
# will actually work.
def calculate_boundaries(message)
calculate_boundaries_low(message, [])
unless message.header['MIME-Version']
message.header['MIME-Version'] = "1.0"
end
end
def calculate_boundaries_low(part, boundaries)
# First, come up with a candidate boundary for this part and
# save it in our list of boundaries.
boundary = make_and_set_unique_boundary(part, boundaries)
# Now walk through each part and make sure the boundaries are
# suitable. We dup the boundaries array before recursing since
# sibling multipart can re-use boundary strings (though it isn't
# a good idea).
boundaries.push(boundary)
part.each_part { |p|
calculate_boundaries_low(p, boundaries) if p.multipart?
}
boundaries.pop
end
# Generate a random boundary
def generate_boundary
@@boundary_count += 1
t = Time.now
sprintf("=-%d-%d-%d-%d-%d-=",
t.tv_sec.to_s,
t.tv_usec.to_s,
Process.pid.to_s,
rand(10000),
@@boundary_count)
end
# Returns a boundary that will probably work out. Extracts any
# existing boundary from the header, but will generate a default
# one if the header doesn't have one set yet.
def make_and_set_unique_boundary(part, boundaries)
candidate = part.header.param('content-type', 'boundary')
unique = make_unique_boundary(candidate || generate_boundary, boundaries)
if candidate.nil? or candidate != unique
part.header.set_boundary(unique)
end
unique
end
# Make the passed boundary unique among the passed boundaries and
# return it.
def make_unique_boundary(boundary, boundaries)
continue = true
while continue
continue = false
boundaries.each do |existing|
if boundary == existing[0, boundary.length]
continue = true
break
end
end
break unless continue
boundary = generate_boundary
end
boundary
end
end
end
syntax highlighted by Code2HTML, v. 0.9.1