class Arachni::Page::DOM

Static DOM snapshot as computed by a real browser.

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

Constants

IGNORE_FROM_HASH

Ignore these elements when calculating a {#hash}.

Attributes

cookies[RW]

@return [Array<Arachni::Element::Cookie>]

data_flow_sinks[RW]

@return [Array]

{Browser::Javascript::TaintTracer#data_flow_sinks} data.
digest[RW]

@return [Integer]

Digest of the DOM tree.
execution_flow_sinks[RW]

@return [Array]

{Browser::Javascript::TaintTracer#execution_flow_sinks} data.
page[RW]

@return [Page]

Page to which this DOM state is attached.
skip_states[RW]

@return [Support::LookUp::HashSet]

transitions[RW]

@return [Array<Transition>]

Transitions representing the steps required to convert a {DOM}
snapshot to a live {Browser} page.
url[RW]

@return [String]

URL of the page as seen by the user-agent, fragments and all.

Public Class Methods

from_rpc_data( data ) click to toggle source

@param [Hash] data

{#to_rpc_data}

@return [DOM]

# File lib/arachni/page/dom.rb, line 258
def self.from_rpc_data( data )
    instance = allocate
    data.each do |name, value|

        value = case name
                    when 'transitions'
                        value.map { |t| Transition.from_rpc_data t }

                    when 'cookies'
                        value.map { |c| Cookie.from_rpc_data c }

                    when 'data_flow_sinks'
                        value.map do |entry|
                            Browser::Javascript::TaintTracer::Sink::DataFlow.from_rpc_data( entry )
                        end.to_a

                    when 'execution_flow_sinks'
                        value.map do |entry|
                            Browser::Javascript::TaintTracer::Sink::ExecutionFlow.from_rpc_data( entry )
                        end.to_a

                    when 'skip_states'
                        skip_states = Support::LookUp::HashSet.new(
                            hasher: :persistent_hash
                        )
                        skip_states.collection.merge( value || [] )
                        skip_states

                    else
                        value
                end

        instance.instance_variable_set( "@#{name}", value )
    end
    instance
end
new( options ) click to toggle source

@param [Hash] options @option options [Page] :page @option options [Array<Hash>] :transitions

# File lib/arachni/page/dom.rb, line 60
def initialize( options )
    @page                 = options[:page]
    self.url              = options[:url]                   || @page.url
    self.digest           = options[:digest]
    @cookies              = options[:cookies]               || []
    @transitions          = options[:transitions]           || []
    @data_flow_sinks      = options[:data_flow_sinks]       || []
    @execution_flow_sinks = options[:execution_flow_sinks]  || []
    @skip_states          = options[:skip_states]           ||
        Support::LookUp::HashSet.new( hasher: :persistent_hash )
end

Public Instance Methods

==( other ) click to toggle source
# File lib/arachni/page/dom.rb, line 299
def ==( other )
    hash == other.hash
end
depth() click to toggle source

@return [Integer]

Depth of the current DOM -- sum of {#transitions} {Transition#depth}s
that had to be triggered to reach the current state.
# File lib/arachni/page/dom.rb, line 85
def depth
    @transitions.map { |t| t.depth }.inject(&:+).to_i
end
hash() click to toggle source
Calls superclass method
# File lib/arachni/page/dom.rb, line 295
def hash
    digest || super
end
inspect()
Alias for: to_s
marshal_dump() click to toggle source
# File lib/arachni/page/dom.rb, line 243
def marshal_dump
    instance_variables.inject({}) do |h, iv|
        next h if iv == :@page
        h[iv] = instance_variable_get( iv )
        h
    end
end
marshal_load( h ) click to toggle source
# File lib/arachni/page/dom.rb, line 251
def marshal_load( h )
    h.each { |k, v| instance_variable_set( k, v ) }
end
playable_transitions() click to toggle source
# File lib/arachni/page/dom.rb, line 89
def playable_transitions
    transitions.select { |t| t.playable? }
end
print_transitions( printer, indent = '' ) click to toggle source
push_transition( transition ) click to toggle source

@param [Transition] transition

Push the given transition to the {#transitions}.
# File lib/arachni/page/dom.rb, line 78
def push_transition( transition )
    @transitions << transition
end
restore( browser ) click to toggle source

Loads the page and restores it to its captured state.

@param [Browser] browser

Browser to use to restore the DOM.

@return [Browser, nil]

Live page in the `browser` if successful, `nil` otherwise.
# File lib/arachni/page/dom.rb, line 137
def restore( browser )
    playables = self.playable_transitions

    # First transition will always be the page load and if that's all there
    # is then we're done.
    if playables.size == 1
        surl = playables.first.options[:url]

        browser.print_debug "Only have a URL load transition: #{surl}"
        browser.goto surl

        return browser

    # Alternatively, try to load the page via its DOM#url in case it can
    # restore itself via its URL fragments and whatnot.
    else
        browser.goto url
    end

    # No transitions, nothing more to be done.
    return browser if playables.empty?

    browser_dom = browser.state

    # We were probably led to an out-of-scope page via a JS redirect, bail out.
    return if !browser_dom

    # Check to see if just loading the DOM URL was enough.
    #
    # Of course, this check will fail some of the time because even if the
    # page can restore itself via its URL (using fragment data most probably),
    # the document may still be different from when our snapshot was captured.
    #
    # However, it doesn't cost us anything so it's worth a shot.
    if browser_dom == self
        browser.print_debug "Loaded snapshot by URL: #{url}"
        return browser
    end

    browser.print_debug "Could not load snapshot by URL (#{url}), " <<
        'will load by replaying transitions.'

    # The URL restore failed, replay its transitions.
    playables.each do |transition|
        next if transition.play( browser )

        browser.print_debug "Could not replay transition for: #{url}"
        playables.each do |t|
            browser.print_debug "-#{t == transition ? '>' : '-'} #{transition}"
        end

        return
    end

    browser
end
state() click to toggle source
# File lib/arachni/page/dom.rb, line 194
def state
    self.class.new(
        url:         @url,
        digest:      @digest,
        transitions: @transitions.dup,
        skip_states: @skip_states.dup
    )
end
to_h() click to toggle source

@return [Hash]

# File lib/arachni/page/dom.rb, line 204
def to_h
    {
        url:                  url,
        transitions:          transitions.map(&:to_hash),
        cookies:              cookies.map(&:to_hash),
        digest:               digest,
        skip_states:          skip_states,
        data_flow_sinks:      data_flow_sinks.map(&:to_hash),
        execution_flow_sinks: execution_flow_sinks.map(&:to_hash)
    }
end
to_hash() click to toggle source
# File lib/arachni/page/dom.rb, line 215
def to_hash
    to_h
end
to_rpc_data() click to toggle source

@return [Hash]

Data representing this instance that are suitable the RPC transmission.
# File lib/arachni/page/dom.rb, line 231
def to_rpc_data
    {
        'url'                  => url,
        'transitions'          => transitions.map(&:to_rpc_data),
        'cookies'              => cookies.map(&:to_rpc_data),
        'digest'               => digest,
        'skip_states'          => skip_states ? skip_states.collection.to_a : [],
        'data_flow_sinks'      => data_flow_sinks.map(&:to_rpc_data),
        'execution_flow_sinks' => execution_flow_sinks.map(&:to_rpc_data)
    }
end
to_s() click to toggle source
# File lib/arachni/page/dom.rb, line 219
def to_s
    s = "#<#{self.class}:#{object_id} "
    s << "@url=#{@url.inspect} "
    s << "@transitions=#{transitions.size} "
    s << "@data_flow_sinks=#{@data_flow_sinks.size} "
    s << "@execution_flow_sinks=#{@execution_flow_sinks.size}"
    s << '>'
end
Also aliased as: inspect
url=( url ) click to toggle source
# File lib/arachni/page/dom.rb, line 72
def url=( url )
    @url = url.freeze
end