class Arachni::Component::Manager

Handles checks, reports, path extractor checks, plug-ins, pretty much every modular aspect of the framework.

It is usually extended to fill-in for system specific functionality.

@example

# create a namespace for our components
module Components
end

LIB       = "#{File.dirname( __FILE__ )}/lib/"
NAMESPACE = Components

# $ ls LIB
#   component1.rb  component2.rb
#
# $ cat LIB/component1.rb
#   class Components::Component1
#   end
#
# $ cat LIB/component2.rb
#   class Components::Component2
#   end

p components = Arachni::Component::Manager.new( LIB, NAMESPACE )
#=> {}

p components.available
#=> ["component2", "component1"]

p components.load_all
#=> ["component2", "component1"]

p components
#=> {"component2"=>Components::Component2, "component1"=>Components::Component1}

p components.clear
#=> {}

p components.load :component1
#=> ["component1"]

p components
#=> {"component1"=>Components::Component1}

p components.clear
#=> {}

p components[:component2]
#=> Components::Component2

@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Constants

EXCLUDE
WILDCARD

Attributes

lib[R]

@return [String]

The path to the component library/directory.
namespace[R]

@return [Module]

Namespace under which all components are directly defined.

Public Class Methods

new( lib, namespace ) click to toggle source

@param [String] lib

The path to the component library/directory.

@param [Module,Class] namespace

Namespace under which all components are directly defined.
# File lib/arachni/component/manager.rb, line 104
def initialize( lib, namespace )
    @lib       = lib
    @namespace = namespace

    @helper_check_cache = {}
    @name_to_path_cache = {}
    @path_to_name_cache = {}
end

Public Instance Methods

[]( name ) click to toggle source

Fetches a component’s class by name, loading it on the fly if need be.

@param [String, Symbol] name

Component name.

@return [Component::Base]

Component.
# File lib/arachni/component/manager.rb, line 285
def []( name )
    name = name.to_s
    return fetch( name ) if include?( name )
    self[name] = load_from_path( name_to_path( name ) )
end
available() click to toggle source

@return [Array]

Names of available components.
# File lib/arachni/component/manager.rb, line 322
def available
    paths.map { |path| path_to_name( path ) }
end
clear() click to toggle source

Unloads all loaded components.

# File lib/arachni/component/manager.rb, line 297
def clear
    keys.each { |l| delete( l ) }
end
Also aliased as: unload_all
delete( name ) click to toggle source

Unloads a component by name.

@param [String, Symbol] name

Component name.
Calls superclass method
# File lib/arachni/component/manager.rb, line 306
def delete( name )
    name = name.to_s
    begin
        @namespace.send(
            :remove_const,
            fetch( name ).to_s.split( ':' ).last.to_sym
        )
    rescue
    end

    super( name )
end
Also aliased as: unload
include?( k ) click to toggle source
Calls superclass method
# File lib/arachni/component/manager.rb, line 291
def include?( k )
    super( k.to_s )
end
Also aliased as: loaded?
load( *components ) click to toggle source

Loads components.

@param [Array<String,Symbol>] components

Components to load.

@return [Array]

Names of loaded components.
# File lib/arachni/component/manager.rb, line 120
def load( *components )
    parse( [components].flatten ).each { |component| self.[]( component ) }
end
load_all() click to toggle source

Loads all components, equivalent of ‘load ’*‘`.

@return [Array]

Names of loaded components.
# File lib/arachni/component/manager.rb, line 128
def load_all
    load '*'
end
load_by_tags( tags ) click to toggle source

Loads components by the tags found in the ‘Hash` returned by their `.info` method (tags should be in either: `:tags` or `:issue`).

@param [Array] tags

Tags to look for in components.

@return [Array]

Components loaded.
# File lib/arachni/component/manager.rb, line 140
def load_by_tags( tags )
    return [] if !tags

    tags = [tags].flatten.compact.map( &:to_s )
    return [] if tags.empty?

    load_all
    map do |k, v|
        component_tags  = [v.info[:tags]]
        component_tags |= [v.info[:issue][:tags]] if v.info[:issue]
        component_tags  = [component_tags].flatten.uniq.compact

        if !component_tags.includes_tags?( tags )
            delete( k )
            next
        end
        k
    end.compact
end
loaded() click to toggle source

@return [Array]

Names of loaded components.
# File lib/arachni/component/manager.rb, line 328
def loaded
    keys
end
loaded?( k )
Alias for: include?
matches_glob?( path, glob ) click to toggle source
# File lib/arachni/component/manager.rb, line 367
def matches_glob?( path, glob )
    relative_path = File.dirname( path.gsub( @lib, '' ) )
    relative_path << '/' if !relative_path.end_with?( '/' )

    name = path_to_name( path )

    Support::Glob.new( glob ) =~ name ||
        Support::Glob.new( glob ) =~ relative_path
end
matches_globs?( path, globs ) click to toggle source
# File lib/arachni/component/manager.rb, line 363
def matches_globs?( path, globs )
    !![globs].flatten.compact.find { |glob| matches_glob?( path, glob ) }
end
name_to_path( name ) click to toggle source

Converts the name of a component to a its file’s path.

@param [String] name

Name of the component.

@return [String]

Path to component file.
# File lib/arachni/component/manager.rb, line 339
def name_to_path( name )
    @name_to_path_cache[name] ||=
        paths.find { |path| name.to_s == path_to_name( path ) }
end
parse( components ) click to toggle source

It parses the component array making sure that its structure is valid and takes into consideration {WILDCARD wildcard} and {EXCLUDE exclusion} modifiers.

@param [Array<String,Symbol>] components

Component names.

@return [Array]

Components to load.
# File lib/arachni/component/manager.rb, line 228
def parse( components )
    unload = []
    load   = []

    components = [components].flatten.map( &:to_s )

    return load if components[0] == EXCLUDE

    components = components.deep_clone

    components.each do |component|
        if component[0] == EXCLUDE
            component[0] = ''

            if component[WILDCARD]
                unload |= glob_to_names( component )
            else
                unload << component
            end

        end
    end

    if !components.include?( WILDCARD )

        avail_components  = available(  )

        components.each do |component|

            if component.include?( WILDCARD )
                load |= glob_to_names( component )
            else

                if avail_components.include?( component )
                    load << component
                else
                    fail Error::NotFound,
                         "Component '#{component}' could not be found."
                end
            end
        end

        load.flatten!
    else
        available.each{ |component| load << component }
    end

    load - unload
end
path_to_name( path ) click to toggle source

Converts the path of a component to a component name.

@param [String] path

File-path of the component.

@return [String]

Component name.
# File lib/arachni/component/manager.rb, line 351
def path_to_name( path )
    @path_to_name_cache[path] ||= File.basename( path, '.rb' )
end
paths() click to toggle source

@return [Array]

Paths of all available components (excluding helper files).
# File lib/arachni/component/manager.rb, line 357
def paths
    @paths_cache ||=
        Dir.glob( File.join( "#{@lib}**", "*.rb" ) ).
            reject{ |path| helper?( path ) }
end
prepare_options( component_name, component, user_opts = {} ) click to toggle source

Validates and prepares options for a given component.

@param [String] component_name

Name of the component.

@param [Component::Base] component

Component.

@param [Hash] user_opts

User options.

@return [Hash]

Prepared options to be passed to the component.

@raise [Component::Options::Error::Invalid]

If given options are invalid.
# File lib/arachni/component/manager.rb, line 174
def prepare_options( component_name, component, user_opts = {} )
    info = component.info
    return {} if !info.include?( :options ) || info[:options].empty?

    user_opts ||= {}
    user_opts   = user_opts.my_symbolize_keys(false)

    options     = {}
    errors      = {}
    info[:options].each do |option|
        option.value = user_opts[option.name]

        if option.missing_value?
            errors[option.name] = {
                option: option,
                value:  option.value,
                type:   :missing_value
            }

            break
        end

        next if option.effective_value.nil?

        if !option.valid?
            errors[option.name] = {
                option: option,
                value:  option.value,
                type:   :invalid
            }

            break
        end

        options.merge! option.for_component
    end

    if !errors.empty?
        fail Component::Options::Error::Invalid,
             format_error_string( component_name, errors )
    end

    options.my_symbolize_keys( false )
end
unload( name )
Alias for: delete
unload_all()
Alias for: clear

Private Instance Methods

classes() click to toggle source
# File lib/arachni/component/manager.rb, line 413
def classes
    @namespace.constants.reject{ |c| !get_obj( c ).is_a?( Class ) }
end
format_error_string( name, errors ) click to toggle source
# File lib/arachni/component/manager.rb, line 389
def format_error_string( name, errors )
    "Invalid options for component: #{name}\n" +
    errors.map do |optname, error|
        val = error[:value].nil? ? '<empty>' : error[:value]
        msg = (error[:type] == :invalid) ? 'Invalid type' : 'Missing value'

        " *  #{msg}: #{optname} => '#{val}'\n" +
        " *  Expected type: #{error[:option].type}"
    end.join( "\n\n" )
end
get_obj( sym ) click to toggle source
# File lib/arachni/component/manager.rb, line 417
def get_obj( sym )
    @namespace.const_get( sym )
end
glob_to_names( glob ) click to toggle source
# File lib/arachni/component/manager.rb, line 379
def glob_to_names( glob )
    if glob[WILDCARD]
        paths.map do |path|
            next if !matches_glob?( path, glob )

            path_to_name( path )
        end.compact
    end
end
helper?( path ) click to toggle source
# File lib/arachni/component/manager.rb, line 421
def helper?( path )
    @helper_check_cache[path] ||= File.exist?( File.dirname( path ) + '.rb' )
end
load_from_path( path ) click to toggle source
# File lib/arachni/component/manager.rb, line 400
def load_from_path( path )
    pre = classes
    ::Kernel::load( path )
    post = classes

    return if pre == post

    get_obj( (post - pre).first ).tap do |component|
        next if !component.respond_to?( :shortname= )
        component.shortname = path_to_name( path )
    end
end