class RIMS::Protocol::SearchParser

Attributes

charset[R]

Public Class Methods

new(mail_store, folder, charset_aliases: RFC822::DEFAULT_CHARSET_ALIASES, charset_convert_options: nil) click to toggle source
# File lib/rims/protocol/parser.rb, line 280
def initialize(mail_store, folder, charset_aliases: RFC822::DEFAULT_CHARSET_ALIASES, charset_convert_options: nil)
  @mail_store = mail_store
  @folder = folder
  @charset_aliases = charset_aliases
  @charset_convert_options = charset_convert_options || {}
  @charset = nil
  @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

charset=(new_charset) click to toggle source
# File lib/rims/protocol/parser.rb, line 300
def charset=(new_charset)
  charset_encoding = @charset_aliases[new_charset] || Encoding.find(new_charset)
  if (charset_encoding.dummy?) then
    # same error type as `Encoding.find'
    raise ArgumentError, "not a searchable charset: #{new_charset}"
  end
  @charset = charset_encoding
end
parse(search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 773
def parse(search_key)
  cond = parse_cached(search_key)
  proc{|msg|
    found = cond.call(msg)
    @mail_cache.clear
    found
  }
end
parse_msg_flag_disabled(name) click to toggle source
# File lib/rims/protocol/parser.rb, line 354
def parse_msg_flag_disabled(name)
  proc{|next_cond|
    proc{|msg|
      (! @mail_store.msg_flag(@folder.mbox_id, msg.uid, name)) && next_cond.call(msg)
    }
  }
end

Private Instance Methods

compile_search_regexp(search_string) click to toggle source
# File lib/rims/protocol/parser.rb, line 326
def compile_search_regexp(search_string)
  Regexp.new(Regexp.quote(search_string), true)
end
encode_charset(string) click to toggle source
# File lib/rims/protocol/parser.rb, line 317
def encode_charset(string)
  if (string.encoding == @charset) then
    string
  else
    string.encode(@charset, **@charset_convert_options)
  end
end
end_of_cond() click to toggle source
# File lib/rims/protocol/parser.rb, line 331
def end_of_cond
  proc{|msg| true }
end
fetch_next_node(search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 637
def fetch_next_node(search_key)
  if (search_key.empty?) then
    raise SyntaxError, 'unexpected end of search key.'
  end

  op = search_key.shift
  op = op.upcase if (op.is_a? String)

  case (op)
  when 'ALL'
    factory = parse_all
  when 'ANSWERED'
    factory = parse_msg_flag_enabled('answered')
  when 'BCC'
    search_string = shift_string_value('BCC', search_key)
    factory = parse_search_header('bcc', search_string)
  when 'BEFORE'
    search_date = shift_date_value('BEFORE', search_key)
    factory = parse_internal_date(search_date) {|d, boundary| d < boundary }
  when 'BODY'
    search_string = shift_string_value('BODY', search_key)
    factory = parse_body(search_string)
  when 'CC'
    search_string = shift_string_value('CC', search_key)
    factory = parse_search_header('cc', search_string)
  when 'DELETED'
    factory = parse_msg_flag_enabled('deleted')
  when 'DRAFT'
    factory = parse_msg_flag_enabled('draft')
  when 'FLAGGED'
    factory = parse_msg_flag_enabled('flagged')
  when 'FROM'
    search_string = shift_string_value('FROM', search_key)
    factory = parse_search_header('from', search_string)
  when 'HEADER'
    header_name = shift_string_value('HEADER', search_key)
    search_string = shift_string_value('HEADER', search_key)
    factory = parse_search_header(header_name, search_string)
  when 'KEYWORD'
    search_string = shift_string_value('KEYWORD', search_key)
    factory = parse_keyword(search_string)
  when 'LARGER'
    octet_size = shift_octet_size_value('LARGER', search_key)
    factory = parse_mail_bytesize(octet_size) {|size, boundary| size > boundary }
  when 'NEW'
    factory = parse_new
  when 'NOT'
    next_node = fetch_next_node(search_key)
    factory = parse_not(next_node)
  when 'OLD'
    factory = parse_old
  when 'ON'
    search_date = shift_date_value('ON', search_key)
    factory = parse_internal_date(search_date) {|d, boundary| d == boundary }
  when 'OR'
    next_node1 = fetch_next_node(search_key)
    next_node2 = fetch_next_node(search_key)
    factory = parse_or(next_node1, next_node2)
  when 'RECENT'
    factory = parse_msg_flag_enabled('recent')
  when 'SEEN'
    factory = parse_msg_flag_enabled('seen')
  when 'SENTBEFORE'
    search_date = shift_date_value('SENTBEFORE', search_key)
    factory = parse_mail_date(search_date) {|d, boundary| d < boundary }
  when 'SENTON'
    search_date = shift_date_value('SENTON', search_key)
    factory = parse_mail_date(search_date) {|d, boundary| d == boundary }
  when 'SENTSINCE'
    search_date = shift_date_value('SENTSINCE', search_key)
    factory = parse_mail_date(search_date) {|d, boundary| d > boundary }
  when 'SINCE'
    search_date = shift_date_value('SINCE', search_key)
    factory = parse_internal_date(search_date) {|d, boundary| d > boundary }
  when 'SMALLER'
    octet_size = shift_octet_size_value('SMALLER', search_key)
    factory = parse_mail_bytesize(octet_size) {|size, boundary| size < boundary }
  when 'SUBJECT'
    search_string = shift_string_value('SUBJECT', search_key)
    factory = parse_search_header('subject', search_string)
  when 'TEXT'
    search_string = shift_string_value('TEXT', search_key)
    factory = parse_text(search_string)
  when 'TO'
    search_string = shift_string_value('TO', search_key)
    factory = parse_search_header('to', search_string)
  when 'UID'
    mset_string = shift_string_value('UID', search_key)
    msg_set = @folder.parse_msg_set(mset_string, uid: true)
    factory = parse_uid(msg_set)
  when 'UNANSWERED'
    factory = parse_msg_flag_disabled('answered')
  when 'UNDELETED'
    factory = parse_msg_flag_disabled('deleted')
  when 'UNDRAFT'
    factory = parse_msg_flag_disabled('draft')
  when 'UNFLAGGED'
    factory = parse_msg_flag_disabled('flagged')
  when 'UNKEYWORD'
    search_string = shift_string_value('UNKEYWORD', search_key)
    factory = parse_unkeyword(search_string)
  when 'UNSEEN'
    factory = parse_msg_flag_disabled('seen')
  when String
    begin
      msg_set = @folder.parse_msg_set(op, uid: false)
      factory = parse_msg_set(msg_set)
    rescue MessageSetSyntaxError
      raise SyntaxError, "unknown search key: #{op}"
    end
  when Array
    case (op[0])
    when :group
      factory = parse_group(op[1..-1])
    else
      raise SyntaxError, "unknown search key: #{op}"
    end
  else
    raise SyntaxError, "unknown search key: #{op}"
  end

  factory
end
force_charset(string) click to toggle source
# File lib/rims/protocol/parser.rb, line 309
def force_charset(string)
  string = string.dup
  string.force_encoding(@charset)
  string.valid_encoding? or raise SyntaxError, "invalid #{@charset} string: #{string.inspect}"
  string
end
get_mail(msg) click to toggle source
# File lib/rims/protocol/parser.rb, line 293
def get_mail(msg)
  @mail_cache[msg.uid] or raise "not found a mail: #{msg.uid}"
end
parse_all() click to toggle source
# File lib/rims/protocol/parser.rb, line 336
def parse_all
  proc{|next_cond|
    proc{|msg|
      next_cond.call(msg)
    }
  }
end
parse_body(search_string) click to toggle source
# File lib/rims/protocol/parser.rb, line 428
def parse_body(search_string)
  if (@charset)
    search_string = force_charset(search_string)
    search_regexp = compile_search_regexp(search_string)
    search_body = proc{|mail|
      if (mail.text? || mail.messge?) then
        search_regexp.match? encode_charset(mail.mime_charset_body_text)
      elsif (mail.multipart?) then
        mail.parts.any?{|next_mail|
          search_body.call(next_mail)
        }
      else
        false
      end
    }
  else
    search_string = search_string.b
    search_regexp = compile_search_regexp(search_string)
    search_body = proc{|mail|
      if (mail.text? || mail.message?)then
        search_regexp.match? mail.mime_binary_body_string
      elsif (mail.multipart?) then
        mail.parts.any?{|next_mail|
          search_body.call(next_mail)
        }
      else
        false
      end
    }
  end

  proc{|next_cond|
    proc{|msg|
      if (mail = get_mail(msg)) then
        search_body.call(mail) && next_cond.call(msg)
      else
        false
      end
    }
  }
end
parse_cached(search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 762
def parse_cached(search_key)
  unless (search_key.empty?) then
    search_key = search_key.dup
    factory = fetch_next_node(search_key)
    _cond = factory.call(parse_cached(search_key))
  else
    _cond = end_of_cond
  end
end
parse_group(search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 587
def parse_group(search_key)
  group_cond = parse_cached(search_key)
  proc{|next_cond|
    proc{|msg|
      group_cond.call(msg) && next_cond.call(msg)
    }
  }
end
parse_internal_date(search_time) { |mail_date, boundary| ... } click to toggle source
# File lib/rims/protocol/parser.rb, line 395
def parse_internal_date(search_time) # :yields: mail_date, boundary
  d = search_time.to_date
  proc{|next_cond|
    proc{|msg|
      yield(@mail_store.msg_date(@folder.mbox_id, msg.uid).utc.to_date, d) && next_cond.call(msg)
    }
  }
end
parse_keyword(search_string) click to toggle source
# File lib/rims/protocol/parser.rb, line 471
def parse_keyword(search_string)
  proc{|next_cond|
    proc{|msg|
      false
    }
  }
end
parse_mail_bytesize(octet_size) { |mail_bytesize, boundary| ... } click to toggle source
# File lib/rims/protocol/parser.rb, line 419
def parse_mail_bytesize(octet_size) # :yields: mail_bytesize, boundary
  proc{|next_cond|
    proc{|msg|
      yield(@mail_store.msg_text(@folder.mbox_id, msg.uid).bytesize, octet_size) && next_cond.call(msg)
    }
  }
end
parse_mail_date(search_time) { |internal_date, boundary| ... } click to toggle source
# File lib/rims/protocol/parser.rb, line 405
def parse_mail_date(search_time) # :yields: internal_date, boundary
  d = search_time.to_date
  proc{|next_cond|
    proc{|msg|
      if (mail_datetime = get_mail(msg).date) then
        yield(mail_datetime.getutc.to_date, d) && next_cond.call(msg)
      else
        false
      end
    }
  }
end
parse_msg_flag_enabled(name) click to toggle source
# File lib/rims/protocol/parser.rb, line 345
def parse_msg_flag_enabled(name)
  proc{|next_cond|
    proc{|msg|
      @mail_store.msg_flag(@folder.mbox_id, msg.uid, name) && next_cond.call(msg)
    }
  }
end
parse_msg_set(msg_set) click to toggle source
# File lib/rims/protocol/parser.rb, line 578
def parse_msg_set(msg_set)
  proc{|next_cond|
    proc{|msg|
      (msg_set.include? msg.num) && next_cond.call(msg)
    }
  }
end
parse_new() click to toggle source
# File lib/rims/protocol/parser.rb, line 480
def parse_new
  proc{|next_cond|
    proc{|msg|
      @mail_store.msg_flag(@folder.mbox_id, msg.uid, 'recent') && \
      (! @mail_store.msg_flag(@folder.mbox_id, msg.uid, 'seen')) && next_cond.call(msg)
    }
  }
end
parse_not(next_node) click to toggle source
# File lib/rims/protocol/parser.rb, line 490
def parse_not(next_node)
  operand = next_node.call(end_of_cond)
  proc{|next_cond|
    proc{|msg|
      (! operand.call(msg)) && next_cond.call(msg)
    }
  }
end
parse_old() click to toggle source
# File lib/rims/protocol/parser.rb, line 500
def parse_old
  proc{|next_cond|
    proc{|msg|
      (! @mail_store.msg_flag(@folder.mbox_id, msg.uid, 'recent')) && next_cond.call(msg)
    }
  }
end
parse_or(next_node1, next_node2) click to toggle source
# File lib/rims/protocol/parser.rb, line 509
def parse_or(next_node1, next_node2)
  operand1 = next_node1.call(end_of_cond)
  operand2 = next_node2.call(end_of_cond)
  proc{|next_cond|
    proc{|msg|
      (operand1.call(msg) || operand2.call(msg)) && next_cond.call(msg)
    }
  }
end
parse_search_header(field_name, search_string) click to toggle source
# File lib/rims/protocol/parser.rb, line 363
def parse_search_header(field_name, search_string)
  if (@charset) then
    search_string = force_charset(search_string)
    search_regexp = compile_search_regexp(search_string)
    search_header = proc{|mail|
      mail.mime_decoded_header_field_value_list(field_name, @charset, charset_convert_options: @charset_convert_options).any?{|field_value|
        search_regexp.match? field_value
      }
    }
  else
    search_string = search_string.b
    search_regexp = compile_search_regexp(search_string)
    search_header = proc{|mail|
      mail.header.field_value_list(field_name).any?{|field_value|
        search_regexp.match? field_value
      }
    }
  end

  proc{|next_cond|
    proc{|msg|
      mail = get_mail(msg)
      if (mail.header.key? field_name) then
        search_header.call(mail) && next_cond.call(msg)
      else
        false
      end
    }
  }
end
parse_text(search_string) click to toggle source
# File lib/rims/protocol/parser.rb, line 520
def parse_text(search_string)
  if (@charset) then
    search_string = force_charset(search_string)
    search_regexp = compile_search_regexp(search_string)
    search_text = proc{|mail|
      if (search_regexp.match? mail.mime_decoded_header_text(@charset, charset_convert_options: @charset_convert_options)) then
        true
      elsif (mail.text? || mail.message?) then
        search_regexp.match? encode_charset(mail.mime_charset_body_text)
      elsif (mail.multipart?) then
        mail.parts.any?{|next_mail|
          search_text.call(next_mail)
        }
      else
        false
      end
    }
  else
    search_string = search_string.b
    search_regexp = compile_search_regexp(search_string)
    search_text = proc{|mail|
      if (search_regexp.match? mail.header.raw_source) then
        true
      elsif (mail.text? || mail.message?) then
        search_regexp.match? mail.mime_binary_body_string
      elsif (mail.multipart?) then
        mail.parts.any?{|next_mail|
          search_text.call(next_mail)
        }
      else
        false
      end
    }
  end

  proc{|next_cond|
    proc{|msg|
      mail = get_mail(msg)
      search_text.call(mail) && next_cond.call(msg)
    }
  }
end
parse_uid(msg_set) click to toggle source
# File lib/rims/protocol/parser.rb, line 564
def parse_uid(msg_set)
  proc{|next_cond|
    proc{|msg|
      (msg_set.include? msg.uid) && next_cond.call(msg)
    }
  }
end
parse_unkeyword(search_string) click to toggle source
# File lib/rims/protocol/parser.rb, line 573
def parse_unkeyword(search_string)
  parse_all
end
shift_date_value(operation_name, search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 609
def shift_date_value(operation_name, search_key)
  unless (search_date_string = search_key.shift) then
    raise SyntaxError, "need for a search date of #{operation_name}."
  end
  unless (search_date_string.is_a? String) then
    raise SyntaxError, "#{operation_name} search date string expected as <String> but was <#{search_date_string.class}>."
  end

  begin
    Time.parse(search_date_string)
  rescue ArgumentError
    raise SyntaxError, "#{operation_name} search date is invalid: #{search_date_string}"
  end
end
shift_octet_size_value(operation_name, search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 625
def shift_octet_size_value(operation_name, search_key)
  unless (octet_size_string = search_key.shift) then
    raise SyntaxError, "need for a octet size of #{operation_name}."
  end
  unless ((octet_size_string.is_a? String) && (octet_size_string =~ /\A \d+ \z/x)) then
    raise SyntaxError, "#{operation_name} octet size is expected as numeric string but was <#{octet_size_string}>."
  end

  octet_size_string.to_i
end
shift_string_value(operation_name, search_key) click to toggle source
# File lib/rims/protocol/parser.rb, line 597
def shift_string_value(operation_name, search_key)
  unless (search_string = search_key.shift) then
    raise SyntaxError, "need for a search string of #{operation_name}."
  end
  unless (search_string.is_a? String) then
    raise SyntaxError, "#{operation_name} search string expected as <String> but was <#{search_string.class}>."
  end

  search_string
end