#--
# Copyright (C) 2002, 2003, 2004 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::Parser::MultipartReader class.
module RMail
class Parser
require 'rmail/parser/pushbackreader'
# A simple interface to facilitate parsing a multipart message.
#
# The typical RubyMail user will have no use for this class.
# Although it is an example of how to use a PushbackReader, the
# typical RubyMail user will never use a PushbackReader either.
# ;-)
class MultipartReader < PushbackReader # :nodoc:
# Creates a MIME multipart parser.
#
# +input+ is an object supporting a +read+ method that takes one
# argument, a suggested number of bytes to read, and returns
# either a string of bytes read or nil if there are no more
# bytes to read.
#
# +boundary+ is the string boundary that separates the parts,
# without the "--" prefix.
#
# This class is a suitable input source when parsing recursive
# multiparts.
def initialize(input, boundary)
super(input)
escaped = Regexp.escape(boundary)
@delimiter_re = /(?:\G|\n)--#{escaped}(--)?\s*?(\n|\z)/
@might_be_delimiter_re = might_be_delimiter_re(boundary)
@caryover = nil
@chunks = []
@eof = false
@delimiter = nil
@delimiter_is_last = false
@in_epilogue = false
@in_preamble = true
end
# Returns the next chunk of data from the input stream as a
# string. The chunk_size is passed down to the read method of
# this object's input stream to suggest a size to it, but don't
# depend on the returned data being of any particular size.
#
# If this method returns nil, you must call next_part to begin
# reading the next MIME part in the data stream.
def read_chunk(chunk_size)
chunk = read_chunk_low(chunk_size)
if chunk
if @in_epilogue
while more = read_chunk_low(chunk_size)
chunk << more
end
end
end
chunk
end
def read_chunk_low(chunk_size)
if @pushback
return standard_read_chunk(chunk_size)
end
input_gave_nil = false
loop {
return nil if @eof || @delimiter
unless @chunks.empty?
chunk, @delimiter, @delimiter_is_last = @chunks.shift
return chunk
end
chunk = standard_read_chunk(chunk_size)
if chunk.nil?
input_gave_nil = true
end
if @caryover
if chunk
@caryover << chunk
end
chunk = @caryover
@caryover = nil
end
if chunk.nil?
@eof = true
return nil
elsif @in_epilogue
return chunk
end
start = 0
found_last_delimiter = false
while !found_last_delimiter and
(start < chunk.length) and
(found = chunk.index(@delimiter_re, start))
if $~[2] == '' and !input_gave_nil
break
end
delimiter = $~[0]
# check if delimiter had the trailing --
if $~.begin(1)
found_last_delimiter = true
end
temp = if found == start
nil
else
chunk[start, found - start]
end
@chunks << [ temp, delimiter, found_last_delimiter ]
start = $~.end(0)
end
chunk = chunk[start..-1] if start > 0
# If something that looks like a delimiter exists at the end
# of this chunk, refrain from returning it.
unless found_last_delimiter or input_gave_nil
start = chunk.rindex(/\n/) || 0
if chunk.index(@might_be_delimiter_re, start)
@caryover = chunk[start..-1]
chunk[start..-1] = ''
chunk = nil if chunk.length == 0
end
end
unless chunk.nil?
@chunks << [ chunk, nil, false ]
end
chunk, @delimiter, @delimiter_is_last = @chunks.shift
if chunk
return chunk
end
}
end
# Start reading the next part. Returns true if there is a next
# part to read, or false if we have reached the end of the file.
def next_part
if @eof
false
else
if @delimiter
@delimiter = nil
@in_preamble = false
@in_epilogue = @delimiter_is_last
end
true
end
end
# Call this to determine if #read is currently returning strings
# from the preamble portion of a mime multipart.
def preamble?
@in_preamble
end
# Call this to determine if #read is currently returning strings
# from the epilogue portion of a mime multipart.
def epilogue?
@in_epilogue
end
# Call this to retrieve the delimiter string that terminated the
# part just read. This is cleared by #next_part.
def delimiter
@delimiter
end
private
def might_be_delimiter_re(boundary)
s = PushbackReader.maybe_contains_re("--" + boundary)
Regexp.new('(?:\A|\n)(?:' + s + '|\z)')
end
end
end
end
syntax highlighted by Code2HTML, v. 0.9.1