class RMail::StreamParser

The RMail::StreamParser is a low level message parsing API. It is useful when you are interested in serially examining all message content but are not interested in a full object representation of the object. See StreamParser.parse.

Public Class Methods

parse(input, handler) click to toggle source

Parse a message from an input source. This method returns nothing. Instead, the supplied handler is expected to implement the same methods as RMail::StreamHandler. The message structure can be inferred from the methods called on the handler. The input can be any Ruby IO source or a String.

This is a low level parsing API. For a message parser that returns an RMail::Message object, see the RMail::Parser class. RMail::Parser is implemented using RMail::StreamParser.

# File lib/rmail/parser.rb, line 169
def parse(input, handler)
  RMail::StreamParser.new(input, handler).parse
end

Private Instance Methods

parse_header(input, depth) click to toggle source
# File lib/rmail/parser.rb, line 202
def parse_header(input, depth)
  data = nil
  header = nil
  boundary = nil
  while chunk = input.read
    data ||= ''
    data << chunk
    if data[0] == ?\n
      # A leading newline in the message is seen when parsing the
      # parts of a multipart message.  It means there are no
      # headers.  The body part starts directly after this
      # newline.
      rest = data[1..-1]
    elsif data[0] == ?\r && data[1] == ?\n
      rest = data[2..-1]
    else
      header, rest = data.split(/\r?\n\r?\n/, 2)
    end
    break if rest
  end
  input.pushback(rest)
  if header
    mime = false
    fields = header.split(/\r?\n(?!\s)/)
    if fields.first =~ /^From /
      @handler.mbox_from(fields.first)
      fields.shift
    end
    fields.each { |field|
      if field =~ /^From /
        @handler.mbox_from(field)
      else
        name, value = RMail::Header::Field.parse(field)
        case name.downcase
        when 'mime-version'
          if value =~ /\b1\.0\b/
            mime = true
          end
        when 'content-type'
          # FIXME: would be nice to have a procedural equivalent
          # to RMail::Header#param.
          header = RMail::Header.new
          header['content-type'] = value
          boundary = header.param('content-type', 'boundary')
        end
        @handler.header_field(field, name, value)
      end
    }
    unless mime or depth > 0
      boundary = nil
    end
  end
  return boundary
end
parse_low(input, depth) click to toggle source
# File lib/rmail/parser.rb, line 193
def parse_low(input, depth)
  multipart_boundary = parse_header(input, depth)
  if multipart_boundary
    parse_multipart_body(input, depth, multipart_boundary)
  else
    parse_singlepart_body(input, depth)
  end
end
parse_multipart_body(input, depth, boundary) click to toggle source
# File lib/rmail/parser.rb, line 257
def parse_multipart_body(input, depth, boundary)
  input = RMail::Parser::MultipartReader.new(input, boundary)
  input.chunk_size = @chunk_size if @chunk_size

  @handler.multipart_body_begin

  # Reach each part, adding it to this entity as appropriate.
  delimiters = []
  while input.next_part
    if input.preamble?
      while chunk = input.read
        @handler.preamble_chunk(chunk)
      end
    elsif input.epilogue?
      while chunk = input.read
        @handler.epilogue_chunk(chunk)
      end
    else
      @handler.part_begin
      parse_low(input, depth + 1)
      @handler.part_end
    end
    delimiters << (input.delimiter || "") unless input.epilogue?
  end
  @handler.multipart_body_end(delimiters, boundary)
end
parse_singlepart_body(input, depth) click to toggle source
# File lib/rmail/parser.rb, line 284
def parse_singlepart_body(input, depth)
  @handler.body_begin
  while chunk = input.read
    @handler.body_chunk(chunk)
  end
  @handler.body_end
end