class MailParser::Message

Mail message @example

require 'mailparser'
f = File.open('hoge.eml')
m = MailParser::Message.new(f, :output_charset=>'utf-8')
m.subject  #=> String
m.body     #=> String
m.part     #=> Array of Mailparser::Message

Attributes

header[R]
part[R]

Public Class Methods

new(src, opt={}) click to toggle source

@param [String, File, MmapScanner, read] src source object @param [Hash] opt options @option opt [Boolean] :decode_mime_header (false) decode MIME header @option opt [Boolean] :decode_mime_filename (false) decode MIME encoded filename @option opt [Boolean] :output_charset (nil) output encoding @option opt [Boolean] :strict (false) raise ParseError exception when message is invalid @option opt [Proc, Method, call] :charset_converter (nil) charset converter. default is MailParser::ConvCharset.conv_charset

# File lib/mailparser.rb, line 188
def initialize(src, opt={})
  if src.is_a? String
    @src = MmapScanner.new src
  elsif src.is_a? File and src.stat.ftype == 'file'
    @src = MmapScanner.new src
  elsif src.is_a? StringIO
    @src = MmapScanner.new src.string
  elsif src.is_a? MmapScanner
    @src = src
  else
    tmpf = Tempfile.new 'mailparser'
    buf = ''
    while src.read(4096, buf)
      tmpf.write buf
    end
    tmpf.close
    @src = File.open(tmpf.path){|f| MmapScanner.new f}
    File.unlink tmpf.path
  end

  @opt = opt
  @from = @to = @cc = @subject = nil
  @type = @subtype = @charset = @content_transfer_encoding = @filename = nil
  @rawheader = nil
  @rawbody = nil
  @part = []
  opt[:charset_converter] ||= ConvCharset.method(:conv_charset)

  read_header
  read_part
end

Public Instance Methods

body() click to toggle source

@return [String] message body decoded and converted charset

# File lib/mailparser.rb, line 227
def body
  body = body_preconv
  if type == 'text' and charset and @opt[:output_charset]
    begin
      body = @opt[:charset_converter].call(charset, @opt[:output_charset], body)
    rescue
      # ignore
    end
  end
  body
end
body_preconv() click to toggle source

@return [String] message body decoded and not converted charset

# File lib/mailparser.rb, line 240
def body_preconv
  body = @rawbody.to_s
  ret = case content_transfer_encoding
        when "quoted-printable" then RFC2045.qp_decode(body)
        when "base64" then RFC2045.b64_decode(body)
        when "uuencode", "x-uuencode", "x-uue" then decode_uuencode(body)
        else body
        end
  if type == 'text' and charset
    ret.force_encoding(charset) rescue nil
  end
  ret
end
cc() click to toggle source

@return [Array<MailParser::RFC2822::Mailbox>] Cc field @return [nil] when Cc field don't exist

# File lib/mailparser.rb, line 291
def cc()
  return @cc if @cc
  if @header.key? "cc" then
    @cc = @header["cc"].flatten
  else
    @cc = []
  end
  return @cc
end
charset() click to toggle source

@return [String] Content-Type charset attribute as lower-case @return [nil] when charset attribute don't exist

# File lib/mailparser.rb, line 336
def charset()
  return @charset if @charset
  if @header.key? "content-type" then
    c = @header["content-type"][0].params["charset"]
    @charset = c && c.downcase
  else
    @charset = nil
  end
  return @charset
end
content_transfer_encoding() click to toggle source

@return [String] Content-Transfer-Encoding mechanism. default is “7bit”

# File lib/mailparser.rb, line 353
def content_transfer_encoding()
  return @content_transfer_encoding if @content_transfer_encoding
  if @header.key? "content-transfer-encoding" then
    @content_transfer_encoding = @header["content-transfer-encoding"][0].mechanism
  else
    @content_transfer_encoding = "7bit"
  end
  return @content_transfer_encoding
end
filename() click to toggle source

@return [String] Content-Disposition filename attribute or Content-Type name attribute @return [nil] when filename attribute don't exist

# File lib/mailparser.rb, line 365
def filename()
  return @filename if @filename
  if @header.key? "content-disposition" and @header["content-disposition"][0].params.key? "filename" then
    @filename = @header["content-disposition"][0].params["filename"]
  elsif @header.key? "content-type" and @header["content-type"][0].params.key? "name" then
    @filename = @header["content-type"][0].params["name"]
  end
  @filename = RFC2047.decode(@filename, @opt) if @opt[:decode_mime_filename] and @filename
  return @filename
end
from() click to toggle source

@return [MailParser::RFC2822::Mailbox] From field @return [nil] when From field don't exist

# File lib/mailparser.rb, line 267
def from()
  return @from if @from
  if @header.key? "from" then
    @from = @header["from"][0][0]
  else
    @from = nil
  end
  return @from
end
message() click to toggle source

@return [MailParser::Message] body type is message/* @return [nil] when type is not message/*

# File lib/mailparser.rb, line 256
def message
  return nil unless type == "message"
  if ['7bit', '8bit'].include? content_transfer_encoding
    @rawbody.pos = 0
    return Message.new(@rawbody, @opt)
  end
  return Message.new(body_preconv, @opt)
end
multipart?() click to toggle source

@return [Boolean] true if multipart type

# File lib/mailparser.rb, line 348
def multipart?()
  return type == "multipart"
end
raw() click to toggle source

@return [String] raw message

# File lib/mailparser.rb, line 377
def raw
  return @src.to_s
end
rawbody() click to toggle source

@return [String] raw body

# File lib/mailparser.rb, line 387
def rawbody
  @rawbody.to_s
end
rawheader() click to toggle source

@return [String] raw header

# File lib/mailparser.rb, line 382
def rawheader
  @rawheader.to_s
end
subject() click to toggle source

@return [String] Subject field

# File lib/mailparser.rb, line 302
def subject()
  return @subject if @subject
  if @header.key? "subject" then
    @subject = @header["subject"].join(" ")
  else
    @subject = ""
  end
  return @subject
end
subtype() click to toggle source

@return [String] Content-Type sub type as lower-case

# File lib/mailparser.rb, line 324
def subtype()
  return @subtype if @subtype
  if @header.key? "content-type" then
    @subtype = @header["content-type"][0].subtype
  else
    @subtype = "plain"
  end
  return @subtype
end
to() click to toggle source

@return [Array<MailParser::RFC2822::Mailbox>] To field @return [nil] when To field don't exist

# File lib/mailparser.rb, line 279
def to()
  return @to if @to
  if @header.key? "to" then
    @to = @header["to"].flatten
  else
    @to = []
  end
  return @to
end
type() click to toggle source

@return [String] Content-Type main type as lower-case

# File lib/mailparser.rb, line 313
def type()
  return @type if @type
  if @header.key? "content-type" then
    @type = @header["content-type"][0].type
  else
    @type = "text"
  end
  return @type
end

Private Instance Methods

decode_plain(str) click to toggle source
# File lib/mailparser.rb, line 437
def decode_plain(str)
  str
end
decode_uuencode(str) click to toggle source
# File lib/mailparser.rb, line 425
def decode_uuencode(str)
  ret = ""
  str.each_line do |line|
    line.chomp!
    next if line =~ /\A\s*\z/
    next if line =~ /\Abegin \d\d\d [^ ]/
    break if line =~ /\Aend\z/
    ret.concat line.unpack("u").first
  end
  ret
end
read_header() click to toggle source
# File lib/mailparser.rb, line 393
def read_header()
  @rawheader = @src.scan_until(/^(?=\r?\n)|\z/)
  @header = Header.new(@opt)
  until @rawheader.eos?
    if @rawheader.skip(/(.*?)[ \t]*:[ \t]*(.*(\r?\n[ \t].*)*(\r?\n)?)/)
      name = @rawheader.matched(1).to_s
      body = @rawheader.matched(2).to_s
      @header.add(name, body)
    else
      @rawheader.skip(/.*\n/) or break
    end
  end
  @src.scan(/\r?\n/)        # skip delimiter line
  @rawbody = @src.rest
end
read_part() click to toggle source
# File lib/mailparser.rb, line 409
def read_part()
  return if type != "multipart" or @src.eos?
  b = @header["content-type"][0].params["boundary"]
  return unless b
  re = /(?:\A|\r?\n)--#{Regexp.escape b}(?:|(--))(?:\r?\n|\z)/
  @src.scan_until(re) or return  # skip preamble
  until @src.eos?
    unless p = @src.scan_until(re)
      @part.push Message.new(@src.rest, @opt)
      break
    end
    @part.push Message.new(p.peek(p.size-@src.matched.length), @opt)
    break if @src.matched(1)
  end
end