#--
# 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::Parser::PushbackReader class.
module RMail
class Parser
class Error < StandardError; end
# A utility class for reading from an input source in an efficient
# chunked manner.
#
# The idea is to read data in descent sized chunks (the default is
# 16k), but provide a way to "push back" some of the chunk if we
# read too much.
#
# This class is useful only as a base class for other readers --
# e.g. a reader that parses MIME multipart documents, or a reader
# that understands one or more mailbox formats.
#
# The typical RubyMail user will have no interest in this class.
# ;-)
class PushbackReader # :nodoc:
# Create a PushbackReader and have it read from a given input
# source.
#
# The input source must either be a String or respond to the
# "read" method in the same way as an IO object.
def initialize(input)
unless defined? input.read(1)
unless input.is_a?(String)
raise ArgumentError, "input object not IO or String"
end
@pushback = input
@input = nil
else
@pushback = nil
@input = input
end
@chunk_size = 16384
end
# Read a chunk of input. The "size" argument is just a
# suggestion, and more or fewer bytes may be returned. If
# "size" is nil, then return the entire rest of the input
# stream.
def read(size = @chunk_size)
case size
when nil
chunk = nil
while temp = read(@chunk_size)
if chunk
chunk << temp
else
chunk = temp
end
end
chunk
when Fixnum
read_chunk(size)
else
raise ArgumentError,
"Read size (#{size.inspect}) must be a Fixnum or nil."
end
end
# Read a chunk of a given size. Unlike #read, #read_chunk must
# be passed a chunk size, and cannot be passed nil.
#
# This is the function that should be re-defined in subclasses
# for specialized behavior.
def read_chunk(size)
standard_read_chunk(size)
end
# The standard implementation of read_chunk. This can be
# convenient to call from derived classes when super() isn't
# easy to use.
def standard_read_chunk(size)
unless size.is_a?(Fixnum) && size > 0
raise ArgumentError,
"Read size (#{size.inspect}) must be greater than 0."
end
if @pushback
chunk = @pushback
@pushback = nil
elsif ! @input.nil?
chunk = @input.read(size)
end
return chunk
end
# Push a string back. This will be the next chunk of data
# returned by #read.
#
# Because it has not been needed and would compromise
# efficiency, only one chunk of data can be pushed back between
# successive calls to #read.
def pushback(string)
raise RMail::Parser::Error,
'You have already pushed a string back.' if @pushback
@pushback = string
end
# Retrieve the chunk size of this reader.
attr_reader :chunk_size
# Set the chunk size of this reader in bytes. This is useful
# mainly for testing, though perhaps some operations could be
# optimized by tweaking this value. The chunk size must be a
# Fixnum greater than 0.
def chunk_size=(size)
unless size.is_a?(Fixnum)
raise ArgumentError, "chunk size must be a Fixnum"
end
unless size >= 1
raise ArgumentError, "invalid size #{size.inspect} given"
end
@chunk_size = size
end
# Returns true if the next call to read_chunk will return nil.
def eof
@pushback.nil? and (@input.nil? or @input.eof)
end
# Creates a regexp that'll match the given boundary string in
# its entirely anywhere in a string, or any partial prefix of
# the boundary string so long as the match is anchored at the
# end of the string. This is useful for various subclasses of
# PushbackReader that need to know if a given input chunk might
# contain (or contain just the beginning of) an interesting
# string.
def self.maybe_contains_re(boundary)
left = Regexp.quote(boundary[0,1])
right = ''
boundary[1..-1].each_byte { |ch|
left << '(?:'
left << Regexp.quote(ch.chr)
right << '|\z)'
}
left + right
end
end
end
end
syntax highlighted by Code2HTML, v. 0.9.1