#
#   Copyright 2012 Wade Alcorn wade@bindshell.net
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

module BeEF
module API

    # Registrar class to handle all registered timed API calls
    class Registrar

        include Singleton
        
        # Create registrar
        def initialize
            @registry = []
            @count = 1
        end

        # Register timed API calls to an owner  
        # @param [Class] owner the owner of the API hook
        # @param [Class] c the API class the owner would like to hook into
        # @param [String] method the method of the class the owner would like to execute
        # @param [Array] params an array of parameters that need to be matched before the owner will be called
        def register(owner, c, method, params = [])
            if self.verify_api_path(c, method)
                if not self.registered?(owner, c, method, params)
                    id = @count
                    @registry << {
                        'id' => id,
                        'owner' => owner,
                        'class' => c,
                        'method' => method,
                        'params' => params
                    }
                    @count += 1
                    return id
                else
                    print_debug "API Registrar: Attempting to re-register API call #{c.to_s} :#{method.to_s}"
                end
            else
                print_error "API Registrar: Attempted to register non-existant API method #{c.to_s} :#{method.to_s}"
            end
        end
        
        # Tests whether the owner is registered for an API hook
        # @param [Class] owner the owner of the API hook
        # @param [Class] c the API class 
        # @param [String] method the method of the class
        # @param [Array] params an array of parameters that need to be matched
        # @return [Boolean] whether or not the owner is registered
        # @todo Change the param matching to use the new :is_matched_params?() method - Issue #479
        def registered?(owner, c, method, params = [])
            @registry.each{|r|
                if r['owner'] == owner and r['class'] == c and r['method'] == method and params == r['params']
                    return true
                end
            }
            return false
        end

        # Match a timed API call to determine if an API.fire() is required
        # @param [Class] c the target API class
        # @param [String] method the method of the target API class
        # @param [Array] params an array of parameters that need to be matched
        # @return [Boolean] whether or not the arguments match an entry in the API registry
        def matched?(c, method, params = [])
            @registry.each{|r|
                if r['class'] == c and r['method'] == method and self.is_matched_params?(r, params)
                    return true
                end
            }
            return false
        end

        # Un-registers an API hook
        # @param [Integer] id the ID of the API hook
        def unregister(id)
            @registry.delete_if{|r|
                r['id'] == id
            }
        end

        # Retrieves all the owners and ID's of an API hook
        # @param [Class] c the target API class
        # @param [String] method the method of the target API class
        # @param [Array] params an array of parameters that need to be matched
        # @return [Array] an array of hashes consisting of two keys :owner and :id 
        def get_owners(c, method, params = [])
            owners = []
            @registry.each{|r|
                if r['class'] == c and r['method'] == method
                    if self.is_matched_params?(r, params)
                        owners << { :owner => r['owner'], :id => r['id']}
                    end
                end
            }
            return owners
        end

        # Verifies that the api_path has been regitered
        # Verifies the API path has been registered.
        # @note This is a security precaution
        # @param [Class] c the target API class to verify
        # @param [String] m the target method to verify
        def verify_api_path(c, m)
            return (c.const_defined?('API_PATHS') and c.const_get('API_PATHS').has_key?(m))
        end

        # Retrieves the registered symbol reference for an API hook
        # @param [Class] c the target API class to verify
        # @param [String] m the target method to verify
        # @return [Symbol] the API path
        def get_api_path(c, m)
            return (self.verify_api_path(c, m)) ? c.const_get('API_PATHS')[m] : nil;
        end

        # Matches stored API params to params
        # @note If a stored API parameter has a NilClass the parameter matching is skipped for that parameter
        # @note By default this method returns true, this is either because the API.fire() did not include any parameters or there were no parameters defined for this registry entry
        # @param [Hash] reg hash of registry element, must contain 'params' key
        # @param [Array] params array of parameters to be compared to the stored parameters
        # @return [Boolean] whether params matches the stored API parameters
        def is_matched_params?(reg, params)
           stored = reg['params']
           if stored.length == params.length
                matched = true
                stored.each_index{|i|
                    next if stored[i] == nil
                    if not stored[i] == params[i]
                        matched = false
                    end
                }
                return false if not matched
           end
            return true
        end

        # Fires all owners registered to this API hook
        # @param [Class] c the target API class
        # @param [String] m the target API method
        # @param [Array] *args parameters passed for the API call
        # @return [Hash, NilClass] returns either a Hash of :api_id and :data if the owners return data, otherwise NilClass 
        def fire(c, m, *args)
            mods = self.get_owners(c, m, args)
            if mods.length > 0
                data = []
                if self.verify_api_path(c, m) and c.ancestors[0].to_s > "BeEF::API"
                    method = self.get_api_path(c, m)
                    mods.each do |mod|
                      begin
                        #Only used for API Development (very verbose)
                        #print_info "API: #{mod} fired #{method}"
                        result = mod[:owner].method(method).call(*args)
                        if not result == nil
                            data << {:api_id => mod[:id], :data => result}
                        end
                      rescue Exception => e
                        print_error "API Fire Error: #{e.message} in #{mod.to_s}.#{method.to_s}()"
                      end
                    end
                else
                    print_error "API Path not defined for Class: #{c.to_s} method:#{method.to_s}"
                end
                return data
            end
            return nil
        end

    end
   
end
end

require 'core/api/module'
require 'core/api/modules'
require 'core/api/extension'
require 'core/api/extensions'
require 'core/api/main/migration'
require 'core/api/main/network_stack/assethandler.rb'
require 'core/api/main/server'
require 'core/api/main/server/hook'
require 'core/api/main/configuration'

