module Arachni::Framework::Parts::Browser

Provides access to the {BrowserCluster} and relevant helpers.

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

Public Instance Methods

browser_cluster() click to toggle source

@return [BrowserCluster, nil]

A lazy-loaded browser cluster or `nil` if
{OptionGroups::BrowserCluster#pool_size} or
{OptionGroups::Scope#dom_depth_limit} are 0 or not
{#host_has_browser?}.
# File lib/arachni/framework/parts/browser.rb, line 23
def browser_cluster
    return if !use_browsers?

    # Initialization may take a while so since we lazy load this make sure
    # that only one thread gets to this code at a time.
    synchronize do
        if !@browser_cluster
            state.set_status_message :browser_cluster_startup
        end

        @browser_cluster ||= BrowserCluster.new(
            on_pop: proc do
                next if !pause?

                print_debug 'Blocking browser cluster on pop.'
                wait_if_paused
            end
        )
        state.clear_status_messages

        @browser_cluster
    end
end
browser_cluster_job_skip_states() click to toggle source

@private

# File lib/arachni/framework/parts/browser.rb, line 58
def browser_cluster_job_skip_states
    return if !@browser_cluster
    browser_cluster.skip_states( browser_job.id )
end
host_has_browser?() click to toggle source

@return [Bool]

`true` if the environment has a browser, `false` otherwise.
# File lib/arachni/framework/parts/browser.rb, line 49
def host_has_browser?
    Arachni::Browser.has_executable?
end
use_browsers?() click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 63
def use_browsers?
    options.browser_cluster.pool_size > 0 &&
        options.scope.dom_depth_limit > 0 && host_has_browser?
end
wait_for_browser_cluster?() click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 53
def wait_for_browser_cluster?
    @browser_cluster && !browser_cluster.done?
end

Private Instance Methods

apply_dom_metadata( browser, dom ) click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 140
def apply_dom_metadata( browser, dom )
    bp = nil

    begin
        bp = browser.load( dom, take_snapshot: false ).to_page
    rescue Selenium::WebDriver::Error::WebDriverError,
        Watir::Exception::Error => e
        print_debug "Could not apply metadata to '#{dom.url}'" <<
                        " because: #{e} [#{e.class}"
        return
    end

    # Request timeout or some other failure...
    return if bp.code == 0

    handle_browser_page bp
end
browser_job() click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 158
def browser_job
    # We'll recycle the same job since all of them will have the same
    # callback. This will force the BrowserCluster to use the same block
    # for all queued jobs.
    #
    # Also, this job should never end so that all analysis operations
    # share the same state.
    @browser_job ||= BrowserCluster::Jobs::DOMExploration.new(
        never_ending: true
    )
end
browser_job_update_skip_states( states ) click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 79
def browser_job_update_skip_states( states )
    return if states.empty?
    browser_cluster.update_skip_states browser_job.id, states
end
handle_browser_page( result, * ) click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 84
def handle_browser_page( result, * )
    page = result.is_a?( Page ) ? result : result.page

    synchronize do
        return if !push_to_page_queue page

        print_status "Got new page from the browser-cluster: #{page.dom.url}"
        print_info "DOM depth: #{page.dom.depth} (Limit: #{options.scope.dom_depth_limit})"

        if page.dom.transitions.any?
            print_info '  Transitions:'
            page.dom.print_transitions( method(:print_info), '    ' )
        end
    end
end
perform_browser_analysis( page ) click to toggle source

Passes the ‘page` to {BrowserCluster#queue} and then pushes the resulting pages to {#push_to_page_queue}.

@param [Page] page

Page to analyze.
# File lib/arachni/framework/parts/browser.rb, line 105
def perform_browser_analysis( page )
    return if !browser_cluster || !accepts_more_pages? ||
        Options.scope.dom_depth_limit.to_i < page.dom.depth + 1 ||
        !page.has_script?

    # We need to schedule a separate job for applying metadata because it
    # needs to have a clean state.
    schedule_dom_metadata_application( page )

    @perform_browser_analysis_cb ||= method(:handle_browser_page)
    browser_cluster.queue(
        browser_job.forward( resource: page.dom.state ),
        @perform_browser_analysis_cb
    )

    true
end
schedule_dom_metadata_application( page ) click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 123
def schedule_dom_metadata_application( page )
    return if page.dom.depth > 0
    return if page.metadata.map { |_, data| data['skip_dom'].values }.
        flatten.compact.any?

    # This optimization only affects Form & Cookie DOM elements,
    # so don't bother if none of the checks are interested in them.
    return if !checks.values.
        find { |c| c.check? page, [Element::Form::DOM, Element::Cookie::DOM], true }

    dom = page.dom.state
    dom.page = nil # Help out the GC.

    @dom_metadata_application_cb ||= method(:apply_dom_metadata)
    browser_cluster.with_browser dom, @dom_metadata_application_cb
end
shutdown_browser_cluster() click to toggle source
# File lib/arachni/framework/parts/browser.rb, line 70
def shutdown_browser_cluster
    return if !@browser_cluster

    browser_cluster.shutdown

    @browser_cluster = nil
    @browser_job     = nil
end