=begin Arachni Copyright (c) 2010-2012 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com> This is free software; you can copy and distribute and modify this program under the term of the GPL v2.0 License (See LICENSE file for details) =end module Arachni require Options.instance.dir['lib'] + 'component_options' # # Component Manager # # Handles modules, reports, path extrator modules, plug-ins, pretty much # every modular aspect of the framework. # # It is usually extended to fill-in for system specific functionality. # # @author: Tasos "Zapotek" Laskos # <tasos.laskos@gmail.com> # <zapotek@segfault.gr> # @version: 0.1 # class ComponentManager < Hash include Arachni::UI::Output # # The following are used by {#parse}: # * '*' means all modules # * module names prefixed with '-' will be excluded # WILDCARD = '*' EXCLUDE = '-' # # @param [String] lib the path to the component library/folder # @param [Module] namespace the namespace of the components # def initialize( lib, namespace ) @lib = lib @namespace = namespace end # # Loads components. # # @param [Array] components array of names of components to load # def load( components ) parse( components ).each { |component| self.[]( component ) } end # # Validates and prepares options for a given component. # # @param [String] component_name the name of the component # @param [Class] component the component # @param [Hash] user_opts the user options # # @return [Hash] the prepared options to be passed to the component # def prep_opts( component_name, component, user_opts = {} ) info = component.info return {} if !info.include?( :options ) || info[:options].empty? user_opts ||= {} options = { } errors = { } info[:options].each { |opt| name = opt.name val = user_opts[name] || opt.default if( opt.empty_required_value?( val ) ) errors[name] = { :opt => opt, :value => val, :type => :empty_required_value } elsif( !opt.valid?( val ) ) errors[name] = { :opt => opt, :value => val, :type => :invalid } end val = !val.nil? ? val : opt.default options[name] = opt.normalize( val ) } if( !errors.empty? ) print_errors( component_name, errors ) end return options end # # It parses the component array making sure that its structure is valid # # @param [Array] components array of component names # # @return [Array] array of modules to load # def parse( components ) unload = [] load = [] return load if components[0] == EXCLUDE components.each { |component| if component[0] == EXCLUDE component[0] = '' if component[WILDCARD] unload |= wilcard_to_names( component ) else unload << component end end } if( !components.include?( WILDCARD ) ) avail_components = available( ) components.each { |component| if component.substring?( WILDCARD ) load |= wilcard_to_names( component ) else if( avail_components.include?( component ) ) load << component else raise( Arachni::Exceptions::ComponentNotFound, "Error: Component #{component} wasn't found." ) end end } load.flatten! else available( ).map { |component| load << component } end return load - unload end # # Returns a component class object by name, loading it on the fly need be. # # @param [String] name component name # # @return [Class] # def []( name ) return fetch( name ) if include?( name ) paths.each { |path| next if name != path_to_name( path ) self[path_to_name( path )] = load_from_path( path ) } return fetch( name ) rescue nil end def wilcard_to_names( name ) if name[WILDCARD] return paths.map { |path| path_to_name( path ) if path.match( Regexp.new( name ) ) }.compact end return end # # Returns array of available component names. # # @return [Array] # def available components = [] paths.each { |path| name = path_to_name( path ) components << name } return components end # # Converts the name of a component to a file-path. # # @param [String] name the name of the component # # @return [String] # def name_to_path( name ) paths.each { |path| return path if name == path_to_name( path ) } return end # # Converts the path of a component to a component name. # # @param [String] path the file-path of the component # # @return [String] # def path_to_name( path ) File.basename( path, '.rb' ) end # # Returns the paths of all available components (excluding helper files). # # @return [Array] # def paths cpaths = paths = Dir.glob( File.join( "#{@lib}**", "*.rb" ) ) return paths.reject { |path| helper?( path ) } end private def print_errors( name, errors ) print_line print_line print_error( "Invalid options for component: #{name}" ) errors.each { |optname, error| val = error[:value].nil? ? '<empty>' : error[:value] if( error[:type] == :invalid ) msg = "Invalid type" else msg = "Empty required value" end print_error( " * #{msg}: #{optname} => #{val}" ) print_error( " * Expected type: #{error[:opt].type}" ) print_line } exit end def load_from_path( path ) ::Kernel::load( path ) return @namespace.const_get( @namespace.constants[-1] ) end def helper?( path ) return File.exist?( File.dirname( path ) + '.rb' ) end end end