class RIMS::Protocol::FetchParser

Public Class Methods

new(mail_store, folder, charset_aliases: RFC822::DEFAULT_CHARSET_ALIASES) click to toggle source
# File lib/rims/protocol/parser.rb, line 878
def initialize(mail_store, folder, charset_aliases: RFC822::DEFAULT_CHARSET_ALIASES)
  @mail_store = mail_store
  @folder = folder
  @mail_cache = Hash.new{|hash, uid|
    if (msg_txt = @mail_store.msg_text(@folder.mbox_id, uid)) then
      hash[uid] = RFC822::Message.new(msg_txt, charset_aliases: charset_aliases)
    end
  }
end

Public Instance Methods

parse(fetch_att) click to toggle source
# File lib/rims/protocol/parser.rb, line 1273
def parse(fetch_att)
  fetch = parse_cached(fetch_att)
  proc{|msg|
    res = fetch.call(msg)
    @mail_cache.clear
    res
  }
end

Private Instance Methods

expand_macro(cmd_list) click to toggle source
# File lib/rims/protocol/parser.rb, line 893
def expand_macro(cmd_list)
  func_list = cmd_list.map{|name| parse_cached(name) }
  proc{|msg|
    func_list.map{|f| f.call(msg) }.join(' '.b)
  }
end
get_body_disposition(mail) click to toggle source
# File lib/rims/protocol/parser.rb, line 913
def get_body_disposition(mail)
  if (disposition_type = mail.content_disposition_upcase) then
    [ disposition_type,
      make_body_params(mail.content_disposition_parameter_list)
    ]
  else
    # not allowed empty body field disposition.
    # RFC 3501 / 9. Formal Syntax:
    #     body-fld-dsp    = "(" string SP body-fld-param ")" / nil
    nil
  end
end
get_body_lang(mail) click to toggle source
# File lib/rims/protocol/parser.rb, line 927
def get_body_lang(mail)
  if (tag_list = mail.content_language_upcase) then
    unless (tag_list.empty?) then
      if (tag_list.length == 1) then
        tag_list[0]
      else
        tag_list
      end
    end
  end
end
get_bodystructure_data(mail, extension: false) click to toggle source
# File lib/rims/protocol/parser.rb, line 940
def get_bodystructure_data(mail, extension: false)
  body_data = []
  if (mail.multipart?) then       # body_type_mpart
    body_data.concat(mail.parts.map{|part_msg| get_bodystructure_data(part_msg, extension: extension) })
    body_data << mail.media_sub_type_upcase

    # body_ext_mpart
    if (extension) then
      body_data << make_body_params(mail.content_type_parameter_list)
      body_data << get_body_disposition(mail)
      body_data << get_body_lang(mail)
      body_data << mail.header['Content-Location']
    end
  else
    if (mail.text?) then          # body_type_text
      # media_text
      body_data << mail.media_main_type_upcase
      body_data << mail.media_sub_type_upcase

      # body_fields
      body_data << make_body_params(mail.content_type_parameter_list)
      body_data << mail.header['Content-Id']
      body_data << mail.header['Content-Description']
      body_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
      body_data << mail.raw_source.bytesize

      # body_fld_lines
      body_data << mail.raw_source.each_line.count
    elsif (mail.message?) then    # body_type_msg
      # message_media
      body_data << mail.media_main_type_upcase
      body_data << mail.media_sub_type_upcase

      # body_fields
      body_data << make_body_params(mail.content_type_parameter_list)
      body_data << mail.header['Content-Id']
      body_data << mail.header['Content-Description']
      body_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
      body_data << mail.raw_source.bytesize

      # envelope
      body_data << get_envelope_data(mail.message)

      # body
      body_data << get_bodystructure_data(mail.message, extension: extension)

      # body_fld_lines
      body_data << mail.raw_source.each_line.count
    else                          # body_type_basic
      # media_basic
      body_data << mail.media_main_type_upcase
      body_data << mail.media_sub_type_upcase

      # body_fields
      body_data << make_body_params(mail.content_type_parameter_list)
      body_data << mail.header['Content-Id']
      body_data << mail.header['Content-Description']
      body_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
      body_data << mail.raw_source.bytesize
    end

    # body_ext_1part
    if (extension) then
      body_data << mail.header['Content-MD5']
      body_data << get_body_disposition(mail)
      body_data << get_body_lang(mail)
      body_data << mail.header['Content-Location']
    end
  end

  body_data
end
get_envelope_data(mail) click to toggle source
# File lib/rims/protocol/parser.rb, line 1014
def get_envelope_data(mail)
  env_data = []
  env_data << mail.header['Date']
  env_data << mail.header['Subject']
  env_data << mail.from&.map(&:to_a)
  env_data << mail.sender&.map(&:to_a)
  env_data << mail.reply_to&.map(&:to_a)
  env_data << mail.to&.map(&:to_a)
  env_data << mail.cc&.map(&:to_a)
  env_data << mail.bcc&.map(&:to_a)
  env_data << mail.header['In-Reply-To']
  env_data << mail.header['Message-Id']
end
get_mail(msg) click to toggle source
# File lib/rims/protocol/parser.rb, line 888
def get_mail(msg)
  @mail_cache[msg.uid] or raise "not found a mail: #{msg.uid}"
end
make_body_params(name_value_pair_list) click to toggle source
# File lib/rims/protocol/parser.rb, line 901
def make_body_params(name_value_pair_list)
  if (name_value_pair_list && ! name_value_pair_list.empty?) then
    name_value_pair_list.flatten
  else
    # not allowed empty body field parameters.
    # RFC 3501 / 9. Formal Syntax:
    #     body-fld-param  = "(" string SP string *(SP string SP string) ")" / nil
    nil
  end
end
parse_body(body, msg_att_name) click to toggle source
# File lib/rims/protocol/parser.rb, line 1029
def parse_body(body, msg_att_name)
  enable_seen = true
  if (body.option) then
    case (body.option.upcase)
    when 'PEEK'
      enable_seen = false
    else
      raise SyntaxError, "unknown fetch body option: #{option}"
    end
  end
  if (@folder.read_only?) then
    enable_seen = false
  end

  if (enable_seen) then
    fetch_flags = parse_flags('FLAGS')
    fetch_flags_changed = proc{|msg|
      unless (@mail_store.msg_flag(@folder.mbox_id, msg.uid, 'seen')) then
        @mail_store.set_msg_flag(@folder.mbox_id, msg.uid, 'seen', true)
        fetch_flags.call(msg) + ' '.b
      else
        ''.b
      end
    }
  else
    fetch_flags_changed = proc{|msg|
      ''.b
    }
  end

  if (body.section_list.empty?) then
    section_text = nil
    section_index_list = []
  else
    if (body.section_list[0] =~ /\A (?<index>\d+(?:\.\d+)*) (?:\.(?<text>.+))? \z/x) then
      section_text = $~[:text]
      section_index_list = $~[:index].split(/\./).map{|i| i.to_i }
    else
      section_text = body.section_list[0]
      section_index_list = []
    end
  end

  is_root = section_index_list.empty?
  unless (section_text) then
    if (is_root) then
      fetch_body_content = proc{|mail|
        mail.raw_source
      }
    else
      fetch_body_content = proc{|mail|
        mail.body.raw_source
      }
    end
  else
    section_text = section_text.upcase
    case (section_text)
    when 'MIME'
      if (section_index_list.empty?) then
        raise SyntaxError, "need for section index at #{section_text}."
      else
        fetch_body_content = proc{|mail|
          if (header = get_body_content(mail, :header)) then
            header.raw_source
          end
        }
      end
    when 'HEADER'
      fetch_body_content = proc{|mail|
        if (header = get_body_content(mail, :header, nest_mail: ! is_root)) then
          header.raw_source
        end
      }
    when 'HEADER.FIELDS', 'HEADER.FIELDS.NOT'
      if (body.section_list.length != 2) then
        raise SyntaxError, "need for argument of #{section_text}."
      end
      field_name_list = body.section_list[1]
      unless ((field_name_list.is_a? Array) && (field_name_list[0] == :group)) then
        raise SyntaxError, "invalid argument of #{section_text}: #{field_name_list}"
      end
      field_name_list = field_name_list[1..-1]
      case (section_text)
      when 'HEADER.FIELDS'
        fetch_body_content = proc{|mail|
          if (header = get_body_content(mail, :header, nest_mail: ! is_root)) then
            field_name_set = field_name_list.map{|n| n.downcase }.to_set
            name_value_pair_list = header.select{|n, v| field_name_set.include? n.downcase }
            encode_header(name_value_pair_list)
          end
        }
      when 'HEADER.FIELDS.NOT'
        fetch_body_content = proc{|mail|
          if (header = get_body_content(mail, :header, nest_mail: ! is_root)) then
            field_name_set = field_name_list.map{|n| n.downcase }.to_set
            name_value_pair_list = header.reject{|n, v| field_name_set.include? n.downcase }
            encode_header(name_value_pair_list)
          end
        }
      else
        raise 'internal error.'
      end
    when 'TEXT'
      fetch_body_content = proc{|mail|
        if (mail_body = get_body_content(mail, :body, nest_mail: ! is_root)) then
          mail_body.raw_source
        end
      }
    else
      raise SyntaxError, "unknown fetch body section text: #{section_text}"
    end
  end

  proc{|msg|
    res = ''.b
    res << fetch_flags_changed.call(msg)
    res << msg_att_name
    res << ' '.b

    mail = get_body_section(get_mail(msg), section_index_list)
    content = fetch_body_content.call(mail) if mail
    if (content) then
      if (body.partial_origin) then
        if (content.bytesize > body.partial_origin) then
          partial_content = content.byteslice((body.partial_origin)..-1)
          if (partial_content.bytesize > body.partial_size) then # because bignum byteslice is failed.
            partial_content = partial_content.byteslice(0, body.partial_size)
          end
          res << Protocol.quote(partial_content)
        else
          res << 'NIL'.b
        end
      else
        res << Protocol.quote(content)
      end
    else
      res << 'NIL'.b
    end
  }
end
parse_bodystructure(msg_att_name, extension: false) click to toggle source
# File lib/rims/protocol/parser.rb, line 1171
def parse_bodystructure(msg_att_name, extension: false)
  proc{|msg|
    ''.b << msg_att_name << ' '.b << encode_bodystructure(get_bodystructure_data(get_mail(msg), extension: extension))
  }
end
parse_cached(fetch_att) click to toggle source
# File lib/rims/protocol/parser.rb, line 1226
def parse_cached(fetch_att)
  fetch_att = fetch_att.upcase if (fetch_att.is_a? String)
  case (fetch_att)
  when 'ALL'
    fetch = expand_macro(%w[ FLAGS INTERNALDATE RFC822.SIZE ENVELOPE ])
  when 'BODY'
    fetch = parse_bodystructure(fetch_att, extension: false)
  when 'BODYSTRUCTURE'
    fetch = parse_bodystructure(fetch_att, extension: true)
  when 'ENVELOPE'
    fetch = parse_envelope(fetch_att)
  when 'FAST'
    fetch = expand_macro(%w[ FLAGS INTERNALDATE RFC822.SIZE ])
  when 'FLAGS'
    fetch = parse_flags(fetch_att)
  when 'FULL'
    fetch = expand_macro(%w[ FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY ])
  when 'INTERNALDATE'
    fetch = parse_internaldate(fetch_att)
  when 'RFC822'
    fetch = parse_body(Protocol.body(section_list: []), fetch_att)
  when 'RFC822.HEADER'
    fetch = parse_body(Protocol.body(option: 'PEEK', section_list: %w[ HEADER ]), fetch_att)
  when 'RFC822.SIZE'
    fetch = parse_rfc822_size(fetch_att)
  when 'RFC822.TEXT'
    fetch = parse_body(Protocol.body(section_list: %w[ TEXT ]), fetch_att)
  when 'UID'
    fetch = parse_uid(fetch_att)
  when Array
    case (fetch_att[0])
    when :group
      fetch = parse_group(fetch_att[1..-1])
    when :body
      body = fetch_att[1]
      fetch = parse_body(body, body.msg_att_name)
    else
      raise SyntaxError, "unknown fetch attribute: #{fetch_att[0]}"
    end
  else
    raise SyntaxError, "unknown fetch attribute: #{fetch_att}"
  end

  fetch
end
parse_envelope(msg_att_name) click to toggle source
# File lib/rims/protocol/parser.rb, line 1178
def parse_envelope(msg_att_name)
  proc{|msg|
    ''.b << msg_att_name << ' '.b << encode_list(get_envelope_data(get_mail(msg)))
  }
end
parse_flags(msg_att_name) click to toggle source
# File lib/rims/protocol/parser.rb, line 1185
def parse_flags(msg_att_name)
  proc{|msg|
    flag_list = MailStore::MSG_FLAG_NAMES.find_all{|flag_name|
      @mail_store.msg_flag(@folder.mbox_id, msg.uid, flag_name)
    }.map{|flag_name|
      "\\".b << flag_name.capitalize
    }.join(' ')
    ''.b << msg_att_name << ' (' << flag_list << ')'
  }
end
parse_group(fetch_attrs) click to toggle source
# File lib/rims/protocol/parser.rb, line 1218
def parse_group(fetch_attrs)
  group_fetch_list = fetch_attrs.map{|fetch_att| parse_cached(fetch_att) }
  proc{|msg|
    '('.b << group_fetch_list.map{|fetch| fetch.call(msg) }.join(' '.b) << ')'.b
  }
end
parse_internaldate(msg_att_name) click to toggle source
# File lib/rims/protocol/parser.rb, line 1197
def parse_internaldate(msg_att_name)
  proc{|msg|
    ''.b << msg_att_name << @mail_store.msg_date(@folder.mbox_id, msg.uid).strftime(' "%d-%b-%Y %H:%M:%S %z"'.b)
  }
end
parse_rfc822_size(msg_att_name) click to toggle source
# File lib/rims/protocol/parser.rb, line 1204
def parse_rfc822_size(msg_att_name)
  proc{|msg|
    ''.b << msg_att_name << ' '.b << get_mail(msg).raw_source.bytesize.to_s
  }
end
parse_uid(msg_att_name) click to toggle source
# File lib/rims/protocol/parser.rb, line 1211
def parse_uid(msg_att_name)
  proc{|msg|
    ''.b << msg_att_name << ' '.b << msg.uid.to_s
  }
end