=begin

    This file is part of the Arachni-RPC EM project and may be subject to
    redistribution and commercial restrictions. Please see the Arachni-RPC EM
    web site for more information on licensing and terms of use.

=end

module Arachni
module RPC
class Server

# Receives {Request} objects and transmits {Response} objects.
#
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
class Handler < Reactor::Connection
    include Protocol

    # @return   [Request]
    #   Working RPC request.
    attr_reader :request

    # @param    [Server]    server
    #   RPC server.
    def initialize( server )
        @server  = server
        @opts    = server.opts.dup
        @request = nil
    end

    # Handles closed connections and cleans up the SSL session.
    #
    # @private
    def on_close( _ )
        @server = nil
    end

    # * Handles {Request}
    # * Sets the {#request}.
    # * Sends back {Response}.
    #
    # @param    [Request]     req
    def receive_request( req )
        @request = req

        # Create an empty response to be filled in little by little.
        res  = Response.new
        peer = peer_ip_address

        begin
            # Make sure the client is allowed to make RPC calls.
            authenticate!

            # Grab the partially filled in response which includes the result
            # of the RPC call and merge it with out prepared response.
            res.merge!( @server.call( self ) )

        # Handle exceptions and convert them to a simple hash, ready to be
        # passed to the client.
        rescue Exception => e
            type = ''

            # If it's an RPC exception pass the type along as is...
            if e.rpc_exception?
                type = e.class.name.split( ':' )[-1]

            # ...otherwise set it to a RemoteException.
            else
                type = 'RemoteException'
            end

            # RPC conventions for exception transmission.
            res.exception = {
                'type'      => type,
                'message'   => e.to_s,
                'backtrace' => e.backtrace
            }

            msg = "#{e.to_s}\n#{e.backtrace.join( "\n" )}"
            @server.logger.error( 'Exception' ){ msg + " [on behalf of #{peer}]" }
        end

        # Pass the result of the RPC call back to the client but *only* if it
        # wasn't async, otherwise {Server#call} will have already taken care of it.
        send_response( res ) if !res.async?
    end

    private

    # Converts incoming hash objects to {Request} objects and calls
    # {#receive_request}.
    #
    # @param    [Hash]      obj
    def receive_object( obj )
        receive_request( Request.new( obj ) )
    end

    # @param    [Symbol]    severity
    #
    #   Severity of the logged event:
    #
    #   * `:debug`
    #   * `:info`
    #   * `:warn`
    #   * `:error`
    #   * `:fatal`
    #   * `:unknown`
    #
    # @param    [String]    category
    #   Category of message (SSL, Call, etc.).
    # @param    [String]    msg
    #   Message to log.
    def log( severity, category, msg )
        sev_sym = Logger.const_get( severity.to_s.upcase.to_sym )
        @server.logger.add( sev_sym, msg, category )
    end

    # Authenticates the client based on the token in the request.
    #
    # It will raise an exception if the token doesn't check-out.
    def authenticate!
        return if valid_token?( @request.token )

        msg = "Token missing or invalid while calling: #{@request.message}"

        @server.logger.error( 'Authenticator' ){
            msg + " [on behalf of #{peer_ip_address}]"
        }

        fail Exceptions::InvalidToken.new( msg )
    end

    # Compares the authentication token in the param with the one of the server.
    #
    # @param    [String]    token
    #
    # @return   [Bool]
    def valid_token?( token )
        token == @server.token
    end

end
end
end
end