Class: Arachni::Page::DOM
- Defined in:
- lib/arachni/page/dom.rb,
lib/arachni/page/dom/transition.rb
Overview
Static DOM snapshot as computed by a real browser.
Defined Under Namespace
Classes: Error, Transition
Constant Summary collapse
Instance Attribute Summary collapse
- #data_flow_sinks ⇒ Array
-
#digest ⇒ String
String digest of the DOM tree.
- #execution_flow_sinks ⇒ Array
-
#page ⇒ Page
readonly
Page to which this DOM state is attached.
- #skip_states ⇒ Support::LookUp::HashSet
- #transitions ⇒ Array<Transition>
-
#url ⇒ String
URL of the page as seen by the user-agent, fragments and all.
Class Method Summary collapse
Instance Method Summary collapse
- #==(other) ⇒ Object
-
#===(other) ⇒ Bool
‘true` if the compared DOM trees are effectively the same, `false` otherwise.
-
#depth ⇒ Integer
Depth of the current DOM – sum of #transitions Transition#depths that had to be triggered to reach the current state.
- #hash ⇒ Object
-
#initialize(options) ⇒ DOM
constructor
A new instance of DOM.
- #playable_transitions ⇒ Object
- #print_transitions(printer, indent = '') ⇒ Object
- #push_transition(transition) ⇒ Object
-
#restore(browser, take_snapshot = true) ⇒ Browser?
Loads the page and restores it to its captured state.
- #to_h ⇒ Hash
- #to_hash ⇒ Object
-
#to_rpc_data ⇒ Hash
Data representing this instance that are suitable the RPC transmission.
- #to_s ⇒ Object (also: #inspect)
Constructor Details
#initialize(options) ⇒ DOM
Returns a new instance of DOM.
57 58 59 60 61 62 63 64 65 66 |
# File 'lib/arachni/page/dom.rb', line 57 def initialize( ) @page = [:page] self.url = [:url] || @page.url self.digest = [:digest] @transitions = [:transitions] || [] @data_flow_sinks = [:data_flow_sinks] || [] @execution_flow_sinks = [:execution_flow_sinks] || [] @skip_states = [:skip_states] || Support::LookUp::HashSet.new( hasher: :persistent_hash ) end |
Instance Attribute Details
#data_flow_sinks ⇒ Array
Returns Browser::Javascript::TaintTracer#data_flow_sinks data.
36 37 38 |
# File 'lib/arachni/page/dom.rb', line 36 def data_flow_sinks @data_flow_sinks end |
#digest ⇒ String
Returns String digest of the DOM tree.
44 45 46 |
# File 'lib/arachni/page/dom.rb', line 44 def digest @digest end |
#execution_flow_sinks ⇒ Array
Returns Browser::Javascript::TaintTracer#execution_flow_sinks data.
40 41 42 |
# File 'lib/arachni/page/dom.rb', line 40 def execution_flow_sinks @execution_flow_sinks end |
#page ⇒ Page (readonly)
Returns Page to which this DOM state is attached.
52 53 54 |
# File 'lib/arachni/page/dom.rb', line 52 def page @page end |
#skip_states ⇒ Support::LookUp::HashSet
27 28 29 |
# File 'lib/arachni/page/dom.rb', line 27 def skip_states @skip_states end |
#transitions ⇒ Array<Transition>
Returns Transitions representing the steps required to convert a Arachni::Page::DOM snapshot to a live Browser page.
32 33 34 |
# File 'lib/arachni/page/dom.rb', line 32 def transitions @transitions end |
#url ⇒ String
Returns URL of the page as seen by the user-agent, fragments and all.
48 49 50 |
# File 'lib/arachni/page/dom.rb', line 48 def url @url end |
Class Method Details
.from_rpc_data(data) ⇒ DOM
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/arachni/page/dom.rb', line 238 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 '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 |
Instance Method Details
#==(other) ⇒ Object
277 278 279 |
# File 'lib/arachni/page/dom.rb', line 277 def ==( other ) hash == other.hash end |
#===(other) ⇒ Bool
Removes the URL strings of both DOMs from each other’s document before comparing.
Returns ‘true` if the compared DOM trees are effectively the same, `false` otherwise.
287 288 289 |
# File 'lib/arachni/page/dom.rb', line 287 def ===( other ) digest_without_urls( other ) == other.digest_without_urls( self ) end |
#depth ⇒ Integer
Returns Depth of the current DOM – sum of #transitions Arachni::Page::DOM::Transition#depths that had to be triggered to reach the current state.
93 94 95 |
# File 'lib/arachni/page/dom.rb', line 93 def depth @transitions.map { |t| t.depth }.inject(&:+).to_i end |
#hash ⇒ Object
272 273 274 275 |
# File 'lib/arachni/page/dom.rb', line 272 def hash # TODO: Maybe raise error if #digest is not set? digest.persistent_hash end |
#playable_transitions ⇒ Object
97 98 99 |
# File 'lib/arachni/page/dom.rb', line 97 def playable_transitions transitions.select { |t| t.playable? } end |
#print_transitions(printer, indent = '') ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/arachni/page/dom.rb', line 101 def print_transitions( printer, indent = '' ) longest_event_size = 0 page.dom.transitions.each do |t| longest_event_size = [t.event.to_s.size, longest_event_size].max end page.dom.transitions.map do |t| padding = longest_event_size - t.event.to_s.size + 1 time = sprintf( '%.4f', t.time.to_f ) if t.event == :request printer.call "#{indent * 2}* [#{time}s] #{t.event}#{' ' * padding} => #{t.element}" else url = nil if t.[:url] url = "(#{t.[:url]})" end printer.call "#{indent}-- [#{time}s] #{t.event}#{' ' * padding} => #{t.element} #{url}" if t.[:cookies] && t.[:cookies].any? printer.call "#{indent * 2}-- Cookies:" t.[:cookies].each do |name, value| printer.call "#{indent * 3}* #{name}\t=> #{value}\n" end end if t.[:inputs] && t.[:inputs].any? t.[:inputs].each do |name, value| printer.call "#{indent * 2}* #{name}\t=> #{value}\n" end end end end end |
#push_transition(transition) ⇒ Object
86 87 88 |
# File 'lib/arachni/page/dom.rb', line 86 def push_transition( transition ) @transitions << transition end |
#restore(browser, take_snapshot = true) ⇒ Browser?
Loads the page and restores it to its captured state.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/arachni/page/dom.rb', line 145 def restore( browser, take_snapshot = true ) # Preload the associated HTTP response since we've already got it. browser.preload( page ) # First, try to load the page via its DOM#url in case it can restore # itself via its URL fragments and whatnot. browser.goto url, take_snapshot: take_snapshot playables = playable_transitions # If we've got no playable transitions then we're done. return browser if playables.empty? browser_page = browser.to_page # We were probably led to an out-of-scope page via a JS redirect, # bail out. return if browser_page.code == 0 # 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, this check doesn't cost us much so it's worth a shot. if browser_page.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, so, navigate to the pure version of the URL and # replay its transitions. browser.preload( page ) 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 |
#to_h ⇒ Hash
198 199 200 201 202 203 204 205 206 207 |
# File 'lib/arachni/page/dom.rb', line 198 def to_h { url: url, transitions: transitions.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 ⇒ Object
208 209 210 |
# File 'lib/arachni/page/dom.rb', line 208 def to_hash to_h end |
#to_rpc_data ⇒ Hash
Returns Data representing this instance that are suitable the RPC transmission.
224 225 226 227 228 229 230 231 232 233 |
# File 'lib/arachni/page/dom.rb', line 224 def to_rpc_data { 'url' => url, 'transitions' => transitions.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 ⇒ Object Also known as: inspect
212 213 214 215 216 217 218 219 |
# File 'lib/arachni/page/dom.rb', line 212 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 |