class RIMS::Protocol::Decoder

Constants

BulkResponse
Engine

alias

IMAP_CMDs
UID_CMDs

Attributes

next_decoder[R]

Public Class Methods

decode_delivery_target_mailbox(encoded_mbox_name) click to toggle source
# File lib/rims/protocol/decoder.rb, line 2035
def Decoder.decode_delivery_target_mailbox(encoded_mbox_name)
  encode_type, base64_username, mbox_name = encoded_mbox_name.split(' ', 3)
  if (encode_type != 'b64user-mbox') then
    raise SyntaxError, "unknown mailbox encode type: #{encode_type}"
  end
  return Protocol.decode_base64(base64_username), mbox_name
end
encode_delivery_target_mailbox(username, mbox_name) click to toggle source
# File lib/rims/protocol/decoder.rb, line 2031
def Decoder.encode_delivery_target_mailbox(username, mbox_name)
  "b64user-mbox #{Protocol.encode_base64(username)} #{mbox_name}"
end
imap_command_normalize(name) click to toggle source
# File lib/rims/protocol/decoder.rb, line 20
def self.imap_command_normalize(name)
  name.upcase
end
logging_error_chain(error, logger) click to toggle source
# File lib/rims/protocol/decoder.rb, line 24
def self.logging_error_chain(error, logger)
  Error.trace_error_chain(error) do |exception|
    if (logger.debug?) then
      Error.optional_data(exception) do |error, data|
        logger.debug("error message: #{error.message} (#{error.class})")
        for name, value in data
          logger.debug("error data [#{name}]: #{value.pretty_inspect}")
        end
      end
    end
    logger.error(exception)
  end
end
make_engine_and_recovery_if_needed(drb_services, username, logger: Logger.new(STDOUT)) { |response| ... } click to toggle source
# File lib/rims/protocol/decoder.rb, line 293
def make_engine_and_recovery_if_needed(drb_services, username, logger: Logger.new(STDOUT))
  unique_user_id = Authentication.unique_user_id(username)
  logger.debug("unique user ID: #{username} -> #{unique_user_id}") if logger.debug?

  logger.info("open mail store: #{unique_user_id} [ #{username} ]")
  engine = drb_services[:engine, unique_user_id]

  begin
    engine.recovery_if_needed(username) {|response| yield(response) }
  rescue
    engine.destroy
    raise
  end

  engine
end
new(auth, logger) click to toggle source
# File lib/rims/protocol/decoder.rb, line 201
def initialize(auth, logger)
  @auth = auth
  @logger = logger
  @next_decoder = self
end
new_decoder(*args, **opts) click to toggle source
# File lib/rims/protocol/decoder.rb, line 13
def self.new_decoder(*args, **opts)
  InitialDecoder.new(*args, **opts)
end
repl(decoder, limits, input, output, logger) click to toggle source
# File lib/rims/protocol/decoder.rb, line 38
def self.repl(decoder, limits, input, output, logger)
  output_write = lambda{|data|
    begin
      if (data == :flush) then
        output.flush
      else
        logger.debug("response data: #{Protocol.io_data_log(data)}") if logger.debug?
        output << data
      end
    rescue
      logger.error('response write error.')
      logging_error_chain($!, logger)
      raise
    end
  }
  server_output_write = lambda{|res|
    for data in res
      output_write.call(data)
    end
    output.flush

    nil
  }
  response_write = lambda{|response|
    output_write.call(response)
    output.flush
    logger.info("server response: #{response.strip}")
  }
  apply_imap_command = lambda{|name, *args, uid: false|
    last_line = nil
    if (uid) then
      decoder.__send__(name, *args, uid: true) {|response|
        output_write.call(response)
        last_line = response if (response.is_a? String)
      }
    else
      decoder.__send__(name, *args) {|response|
        output_write.call(response)
        last_line = response if (response.is_a? String)
      }
    end
    output.flush
    logger.info("server response: #{last_line.strip}") if last_line
  }

  apply_imap_command.call(:ok_greeting)

  conn_timer = ConnectionTimer.new(limits, input.to_io)
  request_reader = decoder.make_requrest_reader(input, output)
  input_gets = request_reader.method(:gets)

  begin
    until (conn_timer.command_wait_timeout?)
      conn_timer.command_wait or break

      begin
        atom_list = request_reader.read_command
      rescue LineTooLongError
        raise
      rescue LiteralSizeTooLargeError
        logger.error('literal size too large error.')
        logging_error_chain($!, logger)
        response_write.call("#{request_reader.command_tag || '*'} BAD literal size too large\r\n")
        next
      rescue CommandSizeTooLargeError
        logger.error('command size too large error.')
        logging_error_chain($!, logger)
        response_write.call("#{request_reader.command_tag || '*'} BAD command size too large\r\n")
        next
      rescue
        logger.error('invalid client command.')
        logging_error_chain($!, logger)
        response_write.call("#{request_reader.command_tag || '*'} BAD client command syntax error\r\n")
        next
      end

      break unless atom_list

      tag, command, *opt_args = atom_list
      normalized_command = imap_command_normalize(command)
      logger.info("client command: #{tag} #{command}")
      if (logger.debug?) then
        case (normalized_command)
        when 'LOGIN'
          log_opt_args = opt_args.dup
          log_opt_args[-1] = '********'
        when 'AUTHENTICATE'
          if (opt_args[1]) then
            log_opt_args = opt_args.dup
            log_opt_args[1] = '********'
          else
            log_opt_args = opt_args
          end
        else
          log_opt_args = opt_args
        end
        logger.debug("client command parameter: #{log_opt_args.inspect}")
      end

      begin
        if (name = IMAP_CMDs[normalized_command]) then
          case (name)
          when :uid
            unless (opt_args.empty?) then
              uid_command, *uid_args = opt_args
              logger.info("uid command: #{uid_command}")
              logger.debug("uid parameter: #{uid_args}") if logger.debug?
              if (uid_name = UID_CMDs[imap_command_normalize(uid_command)]) then
                apply_imap_command.call(uid_name, tag, *uid_args, uid: true)
              else
                logger.error("unknown uid command: #{uid_command}")
                response_write.call("#{tag} BAD unknown uid command\r\n")
              end
            else
              logger.error('empty uid parameter.')
              response_write.call("#{tag} BAD empty uid parameter\r\n")
            end
          when :authenticate
            apply_imap_command.call(:authenticate, tag, input_gets, server_output_write, *opt_args)
          when :idle
            apply_imap_command.call(:idle, tag, input_gets, server_output_write, conn_timer, *opt_args)
          else
            apply_imap_command.call(name, tag, *opt_args)
          end
        else
          logger.error("unknown command: #{command}")
          response_write.call("#{tag} BAD unknown command\r\n")
        end
      rescue LineTooLongError
        raise
      rescue
        logger.error('unexpected error.')
        logging_error_chain($!, logger)
        response_write.call("#{tag} BAD unexpected error\r\n")
      end

      if (normalized_command == 'LOGOUT') then
        break
      end

      decoder = decoder.next_decoder
    end
  rescue LineTooLongError
    logger.error('line too long error.')
    logging_error_chain($!, logger)
    response_write.call("* BAD line too long\r\n")
    response_write.call("* BYE server autologout: connection terminated\r\n")
  else
    if (conn_timer.command_wait_timeout?) then
      if (limits.command_wait_timeout_seconds > 0) then
        response_write.call("* BYE server autologout: idle for too long\r\n")
      else
        response_write.call("* BYE server autologout: shutdown\r\n")
      end
    end
  end

  nil
ensure
  # don't forget to clean up if the next decoder has been generated
  decoder.next_decoder.cleanup
end

Private Class Methods

imap_command(name) click to toggle source
# File lib/rims/protocol/decoder.rb, line 282
def imap_command(name)
  should_be_imap_command(name)
  orig_name = "_#{name}".to_sym
  alias_method orig_name, name
  define_method name, lambda{|tag, *args, **kw_args, &block|
    guard_error(orig_name, tag, *args, **kw_args, &block)
  }
  name.to_sym
end
kw_params(method) click to toggle source
# File lib/rims/protocol/decoder.rb, line 246
def kw_params(method)
  params = method.parameters
  params.find_all{|arg_type, arg_name|
    case (arg_type)
    when :key, :keyreq
      true
    else
      false
    end
  }.map{|arg_type, arg_name|
    arg_name
  }
end
should_be_imap_command(name) click to toggle source
# File lib/rims/protocol/decoder.rb, line 261
def should_be_imap_command(name)
  cmd = to_imap_command(name)
  unless (IMAP_CMDs.key? cmd) then
    raise ArgumentError, "not an IMAP command: #{name}"
  end

  method = instance_method(name)
  if (UID_CMDs.key? cmd) then
    unless (kw_params(method).include? :uid) then
      raise ArgumentError, "not defined `uid' keyword parameter: #{name}"
    end
  else
    if (kw_params(method).include? :uid) then
      raise ArgumentError, "not allowed `uid' keyword parameter: #{name}"
    end
  end

  nil
end
to_imap_command(name) click to toggle source
# File lib/rims/protocol/decoder.rb, line 241
def to_imap_command(name)
  imap_command_normalize(name.to_s)
end

Public Instance Methods

capability(tag) { |"* CAPABILITY #{join(' ')}\r\n"| ... } click to toggle source
# File lib/rims/protocol/decoder.rb, line 324
def capability(tag)
  capability_list = %w[ IMAP4rev1 UIDPLUS IDLE ]
  capability_list += @auth.capability.map{|auth_capability| "AUTH=#{auth_capability}" }
  yield("* CAPABILITY #{capability_list.join(' ')}\r\n")
  yield("#{tag} OK CAPABILITY completed\r\n")
end
ok_greeting() { |"* OK RIMS v#{VERSION} IMAP4rev1 service ready.\r\n"| ... } click to toggle source
# File lib/rims/protocol/decoder.rb, line 317
def ok_greeting
  yield("* OK RIMS v#{VERSION} IMAP4rev1 service ready.\r\n")
end

Private Instance Methods

guard_error(imap_command, tag, *args, **kw_args) { |"#{tag} BAD client command syntax error\r\n"| ... } click to toggle source
# File lib/rims/protocol/decoder.rb, line 214
def guard_error(imap_command, tag, *args, **kw_args, &block)
  begin
    if (kw_args.empty?) then
      __send__(imap_command, tag, *args, &block)
    else
      __send__(imap_command, tag, *args, **kw_args, &block)
    end
  rescue LineTooLongError
    raise
  rescue SyntaxError
    @logger.error('client command syntax error.')
    logging_error_chain($!)
    yield("#{tag} BAD client command syntax error\r\n")
  rescue ArgumentError
    @logger.error('invalid command parameter.')
    logging_error_chain($!)
    yield("#{tag} BAD invalid command parameter\r\n")
  rescue
    raise if ($!.class.name =~ /AssertionFailedError/)
    @logger.error('internal server error.')
    logging_error_chain($!)
    yield("#{tag} BAD internal server error\r\n")
  end
end
logging_error_chain(error) click to toggle source
# File lib/rims/protocol/decoder.rb, line 209
def logging_error_chain(error)
  self.class.logging_error_chain(error, @logger)
end
make_logout_response(tag) { |"* BYE server logout\r\n"| ... } click to toggle source
# File lib/rims/protocol/decoder.rb, line 311
def make_logout_response(tag)
  yield("* BYE server logout\r\n")
  yield("#{tag} OK LOGOUT completed\r\n")
end