Class: Arachni::Browser

Inherits:
Object show all
Includes:
Support::Mixins::Observable, UI::Output, Utilities
Defined in:
lib/arachni/browser.rb,
lib/arachni/browser/javascript.rb,
lib/arachni/browser/element_locator.rb,
lib/arachni/browser/javascript/dom_monitor.rb,
lib/arachni/browser/javascript/taint_tracer.rb,
lib/arachni/browser/javascript/taint_tracer/frame.rb,
lib/arachni/browser/javascript/taint_tracer/sink/base.rb,
lib/arachni/browser/javascript/taint_tracer/sink/data_flow.rb,
lib/arachni/browser/javascript/taint_tracer/sink/execution_flow.rb,
lib/arachni/browser/javascript/taint_tracer/frame/called_function.rb

Overview

Note:

Depends on PhantomJS 1.9.2.

Real browser driver providing DOM/JS/AJAX support.

Author:

Direct Known Subclasses

Arachni::BrowserCluster::Worker

Defined Under Namespace

Classes: ElementLocator, Error, Javascript

Constant Summary collapse

PHANTOMJS_SPAWN_TIMEOUT =

How much time to wait for the PhantomJS process to spawn before respawning.

4
ELEMENT_APPEARANCE_TIMEOUT =

How much time to wait for a targeted HTML element to appear on the page after the page is loaded.

5
WATIR_COM_TIMEOUT =

Let the browser take as long as it needs to complete an operation.

3600
HTML_IDENTIFIERS =

1 hour.

['<!doctype html', '<html', '<head', '<body', '<title', '<script']

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Support::Mixins::Observable

included

Methods included from Utilities

#available_port, #caller_name, #caller_path, #cookie_decode, #cookie_encode, #cookies_from_document, #cookies_from_file, #cookies_from_response, #exception_jail, #exclude_path?, #follow_protocol?, #form_decode, #form_encode, #forms_from_document, #forms_from_response, #generate_token, #get_path, #hms_to_seconds, #html_decode, #html_encode, #include_path?, #links_from_document, #links_from_response, #normalize_url, #page_from_response, #page_from_url, #parse_set_cookie, #path_in_domain?, #path_too_deep?, #port_available?, #rand_port, #random_seed, #redundant_path?, #regexp_array_match, #remove_constants, #request_parse_body, #seconds_to_hms, #skip_page?, #skip_resource?, #skip_response?, #to_absolute, #uri_decode, #uri_encode, #uri_parse, #uri_parse_query, #uri_parser, #uri_rewrite

Methods included from UI::Output

#debug?, #debug_off, #debug_on, #disable_only_positives, #included, #mute, #muted?, #only_positives, #only_positives?, #print_bad, #print_debug, #print_debug_backtrace, #print_debug_level_1, #print_debug_level_2, #print_debug_level_3, #print_error, #print_error_backtrace, #print_exception, #print_info, #print_line, #print_ok, #print_status, #print_verbose, #reroute_to_file, #reroute_to_file?, reset_output_options, #unmute, #verbose?, #verbose_on

Constructor Details

#initialize(options = {}) ⇒ Browser

Returns a new instance of Browser.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :concurrency (Integer)

    Maximum number of concurrent connections.

  • :store_pages (Bool) — default: true

    Whether to store pages in addition to just passing them to #on_new_page.

  • :width (Integer) — default: 1600

    Window width.

  • :height (Integer) — default: 1200

    Window height.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
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
# File 'lib/arachni/browser.rb', line 131

def initialize( options = {} )
    super()
    @options = options.dup

    @ignore_scope = options[:ignore_scope]

    @width  = options[:width]  || 1600
    @height = options[:height] || 1200

    @proxy = HTTP::ProxyServer.new(
        concurrency:      @options[:concurrency],
        address:          '127.0.0.1',
        request_handler:  proc do |request, response|
            synchronize { exception_jail { request_handler( request, response ) } }
        end,
        response_handler: proc do |request, response|
            synchronize { exception_jail { response_handler( request, response ) } }
        end
    )

    @options[:store_pages] = true if !@options.include?( :store_pages )

    @proxy.start_async

    @watir = ::Watir::Browser.new( selenium )

    # User-controlled response cache, by URL.
    @cache = Support::Cache::LeastRecentlyUsed.new( 200 )

    # User-controlled preloaded responses, by URL.
    @preloads = {}

    # Captured pages -- populated by #capture.
    @captured_pages = []

    # Snapshots of the working page resulting from firing of events and
    # clicking of JS links.
    @page_snapshots = {}

    # Same as @page_snapshots but it doesn't deduplicate and only contains
    # pages with sink (Page::DOM#sink) data as populated by Javascript#flush_sink.
    @page_snapshots_with_sinks = []

    # Captures HTTP::Response objects per URL for open windows.
    @window_responses = {}

    # Keeps track of resources which should be skipped -- like already fired
    # events and clicked links etc.
    @skip_states = Support::LookUp::HashSet.new( hasher: :persistent_hash )

    @transitions = []
    @request_transitions = []
    @add_request_transitions = true

    # Last loaded URL.
    @last_url = nil

    @javascript = Javascript.new( self )

    ensure_open_window
end

Instance Attribute Details

#javascriptJavascript (readonly)

Returns:



98
99
100
# File 'lib/arachni/browser.rb', line 98

def javascript
  @javascript
end

#page_snapshots_with_sinksArray<Page> (readonly)

Returns Same as #page_snapshots but it doesn’t deduplicate and only contains pages with sink (Page::DOM#data_flow_sinks or Page::DOM#execution_flow_sinks) data as populated by Arachni::Browser::Javascript#data_flow_sinks and Arachni::Browser::Javascript#execution_flow_sinks.



95
96
97
# File 'lib/arachni/browser.rb', line 95

def page_snapshots_with_sinks
  @page_snapshots_with_sinks
end

#pidInteger (readonly)

Returns:

  • (Integer)


108
109
110
# File 'lib/arachni/browser.rb', line 108

def pid
  @pid
end

#preloadsHash (readonly)

Returns Preloaded resources, by URL.

Returns:

  • (Hash)

    Preloaded resources, by URL.



80
81
82
# File 'lib/arachni/browser.rb', line 80

def preloads
  @preloads
end

#skip_statesSupport::LookUp::HashSet (readonly)

Returns States that have been visited and should be skipped.

Returns:

See Also:

  • #skip_state
  • #skip_state?


105
106
107
# File 'lib/arachni/browser.rb', line 105

def skip_states
  @skip_states
end

#transitionsArray<Page::DOM::Transition> (readonly)



76
77
78
# File 'lib/arachni/browser.rb', line 76

def transitions
  @transitions
end

#watirWatir::Browser (readonly)

Returns Watir driver interface.

Returns:

  • (Watir::Browser)

    Watir driver interface.



84
85
86
# File 'lib/arachni/browser.rb', line 84

def watir
  @watir
end

Class Method Details

.executableString

Returns Path to the PhantomJS executable.

Returns:

  • (String)

    Path to the PhantomJS executable.



118
119
120
# File 'lib/arachni/browser.rb', line 118

def self.executable
    Selenium::WebDriver::PhantomJS.path
end

.has_executable?Bool

Returns ‘true` if a supported browser is in the OS PATH, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if a supported browser is in the OS PATH, `false` otherwise.



112
113
114
# File 'lib/arachni/browser.rb', line 112

def self.has_executable?
    !!executable
end

Instance Method Details

#cache(resource = nil) ⇒ Object

Parameters:

  • resource (HTTP::Response, Page) (defaults to: nil)

    Cache a resource in order to be instantly available by URL via #load.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/arachni/browser.rb', line 263

def cache( resource = nil )
    return @cache if !resource

    response =  case resource
                    when HTTP::Response
                        resource

                    when Page
                        resource.response

                    else
                        fail Error::Load,
                             "Can't load resource of type #{resource.class}."
                end

    save_response response
    @cache[response.url] = response
    response.url
end

#capture?Bool

Returns ‘true` if request capturing is enabled, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if request capturing is enabled, `false` otherwise.

See Also:



696
697
698
# File 'lib/arachni/browser.rb', line 696

def capture?
    !!@capture
end

#capture_snapshot(transition = nil) ⇒ Object



732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
# File 'lib/arachni/browser.rb', line 732

def capture_snapshot( transition = nil )
    pages = []

    request_transitions = flush_request_transitions
    transitions = ([transition] + request_transitions).flatten.compact

    begin
        # Skip about:blank windows.
        watir.windows( url: /^http/ ).each do |window|
            window.use do
                next if !(page = to_page)

                if pages.empty?
                    transitions.each do |t|
                        @transitions << t
                        page.dom.push_transition t
                    end
                end

                capture_snapshot_with_sink( page )

                unique_id = self.snapshot_id
                next if skip_state? unique_id
                skip_state unique_id

                notify_on_new_page( page )

                if store_pages?
                    @page_snapshots[unique_id.hash] = page
                    pages << page
                end
            end
        end
    rescue => e
        print_debug "Could not capture snapshot for: #{@last_url}"

        if transition
            print_debug "-- #{transition}"
        end

        print_debug
        print_debug_exception e
    end

    pages
end

#captured_pagesArray<Page>

Returns Captured HTTP requests performed by the web page (AJAX etc.) converted into forms of pages to assist with analysis and audit.

Returns:

  • (Array<Page>)

    Captured HTTP requests performed by the web page (AJAX etc.) converted into forms of pages to assist with analysis and audit.



710
711
712
# File 'lib/arachni/browser.rb', line 710

def captured_pages
    @captured_pages
end

#cookiesArray<Cookie>

Returns Browser cookies.

Returns:



805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
# File 'lib/arachni/browser.rb', line 805

def cookies
    js_cookies = begin
        # Watir doesn't tell us if cookies are HttpOnly, so we need to figure
        # this out ourselves, by checking for JS visibility.
        javascript.run( 'return document.cookie' )
    # We may not have a page.
    rescue Selenium::WebDriver::Error::UnknownError
        ''
    end

    watir.cookies.to_a.map do |c|
        original_name = c[:name].to_s

        c[:path]     = '/' if c[:path] == '//'
        c[:name]     = Cookie.decode( c[:name].to_s )
        c[:value]    = Cookie.decode( c[:value].to_s )
        c[:httponly] = !js_cookies.include?( original_name )

        Cookie.new c.merge( url: @last_url || url )
    end
end

#distribute_event(page, locator, event) ⇒ Object

Note:

Only used when running as part of Arachni::BrowserCluster to distribute page analysis across a pool of browsers.

Distributes the triggering of ‘event` on the element at `element_index` on `page`.

Parameters:



520
521
522
# File 'lib/arachni/browser.rb', line 520

def distribute_event( page, locator, event )
    trigger_event( page, locator, event )
end

#each_element_with_events(mark_state = true) {|ElementLocator, Array<Symbol>| ... } ⇒ Object

Note:

Will skip non-visible elements as they can’t be manipulated.

Iterates over all elements which have events and passes their info to the given block.

Parameters:

  • mark_state (Bool) (defaults to: true)

    Mark each element/events as visited and skip it if it has already been seen.

Yields:

  • (ElementLocator, Array<Symbol>)

    Hash with information about the element, its tag name, applicable events along with their handlers and attributes.



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/arachni/browser.rb', line 387

def each_element_with_events( mark_state = true )
    current_url = url

    javascript.dom_elements_with_events.each do |element|
        tag_name   = element['tag_name']
        attributes = element['attributes']
        events     = element['events']

        case tag_name
            when 'a'
                href = attributes['href'].to_s

                if !href.empty?
                    if href.start_with?( 'javascript:' )
                        events << [ :click, href ]
                    else
                        next if skip_path?( to_absolute( href, current_url ) )
                    end
                end

            when 'input'
                if attributes['type'].to_s.downcase == 'image'
                    events << [ :click, 'image' ]
                end

            when 'form'
                action = attributes['action'].to_s

                if !action.empty?
                    if action.start_with?( 'javascript:' )
                        events << [ :submit, action ]
                    else
                        next if skip_path?( to_absolute( action, current_url ) )
                    end
                end
        end

        state = "#{tag_name}#{attributes}#{events}"
        next if events.empty? || (mark_state && skip_state?( state ))
        skip_state state if mark_state

        yield ElementLocator.new( tag_name: tag_name, attributes: attributes ),
                events
    end

    self
end

#explore_and_flush(depth = nil) ⇒ Array<Page>

Explores the browser’s DOM tree and captures page snapshots for each state change until there are no more available.

Parameters:

  • depth (Integer) (defaults to: nil)

    How deep to go into the DOM tree.

Returns:

  • (Array<Page>)

    Page snapshots for each state.



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/arachni/browser.rb', line 359

def explore_and_flush( depth = nil )
    pages         = [ to_page ]
    current_depth = 0

    loop do
        bcnt   = pages.size
        pages |= pages.map { |p| load( p ).trigger_events.flush_pages }.flatten

        break if pages.size == bcnt || (depth && depth >= current_depth)

        current_depth += 1
    end

    pages.compact
end

#fire_event(element, event, options = {}) ⇒ Page::DOM::Transition, false

Triggers ‘event` on `element`.

Parameters:

Options Hash (options):

Returns:



563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/arachni/browser.rb', line 563

def fire_event( element, event, options = {} )
    event   = event.to_s.downcase.sub( /^on/, '' ).to_sym
    locator = nil

    options[:inputs] = options[:inputs].my_stringify if options[:inputs]

    if element.is_a? ElementLocator
        locator = element

        begin
            element = element.locate( self )
        rescue Selenium::WebDriver::Error::UnknownError,
            Watir::Exception::UnknownObjectException => e

            print_debug "Element '#{locator}' could not be located for triggering '#{event}'."
            print_debug
            print_debug_exception e
            return
        end
    end

    # The page may need a bit to settle and the element is lazily located
    # by Watir so give it a few tries.
    begin
        with_timeout ELEMENT_APPEARANCE_TIMEOUT do
            sleep 0.1 while !element.exists?
        end
    rescue Timeout::Error
        print_debug_level_2 "#{locator} did not appear in #{ELEMENT_APPEARANCE_TIMEOUT}."
        return
    end

    if !element.visible?
        print_debug_level_2 "#{locator} is not visible, skipping..."
        return
    end

    if locator
        opening_tag = locator.to_s
        tag_name    = locator.tag_name
    else
        opening_tag = element.opening_tag
        tag_name    = element.tag_name
        locator     = ElementLocator.from_html( opening_tag )
    end

    print_debug_level_2 "#{__method__}: #{event} (#{options}) #{locator}"

    tag_name = tag_name.to_sym

    notify_on_fire_event( element, event )

    tries = 0
    begin
        Page::DOM::Transition.new( locator, event, options ) do
            had_special_trigger = false

            if tag_name == :form
                fill_in_form_inputs( element, options[:inputs] )

                if event == :submit
                    had_special_trigger = true
                    element.submit
                end

            elsif tag_name == :input && event == :click &&
                    element.attribute_value(:type) == 'image'

                had_special_trigger = true
                watir.button( type: 'image' ).click

            elsif [:keyup, :keypress, :keydown, :change, :input, :focus, :blur, :select].include? event

                # Some of these need an explicit event triggers.
                had_special_trigger = true if ![:change, :blur, :focus, :select].include? event

                element.send_keys( (options[:value] || value_for( element )).to_s )
            end

            element.fire_event( event ) if !had_special_trigger
            wait_for_pending_requests
        end
    rescue Selenium::WebDriver::Error::InvalidElementStateError,
        Selenium::WebDriver::Error::UnknownError,
        Watir::Exception::UnknownObjectException => e

        sleep 0.1

        tries += 1
        retry if tries < 5

        print_debug "Error when triggering event for: #{url}"
        print_debug "-- '#{event}' on: #{opening_tag}"
        print_debug
        print_debug_exception e

        nil
    end
end

#flush_page_snapshots_with_sinksArray<Page>

Returns #page_snapshots_with_sinks and flushes it.

Returns:



781
782
783
784
785
# File 'lib/arachni/browser.rb', line 781

def flush_page_snapshots_with_sinks
    @page_snapshots_with_sinks.dup
ensure
    @page_snapshots_with_sinks.clear
end

#flush_pagesArray<Page>

Returns Flushes and returns the captured and snapshot pages.

Returns:

See Also:



796
797
798
799
800
801
# File 'lib/arachni/browser.rb', line 796

def flush_pages
    captured_pages + page_snapshots
ensure
    @captured_pages.clear
    @page_snapshots.clear
end

#goto(url, options = {}) ⇒ Page::DOM::Transition

Returns Transition used to replay the resource visit.

Parameters:

  • url (String)

    Loads the given URL in the browser.

  • options (Hash) (defaults to: {})
  • [Bool] (Hash)

    a customizable set of options

  • [Array<Cookie>] (Hash)

    a customizable set of options

Returns:



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/arachni/browser.rb', line 293

def goto( url, options = {} )
    take_snapshot      = options.include?(:take_snapshot) ?
        options[:take_snapshot] : true
    extra_cookies      = options[:cookies] || {}
    update_transitions = options.include?(:update_transitions) ?
        options[:update_transitions] : true

    pre_add_request_transitions = @add_request_transitions
    if !update_transitions
        @add_request_transitions = false
    end

    @last_url = url

    ensure_open_window

    load_cookies url, extra_cookies

    transition = Page::DOM::Transition.new( :page, :load,
        url:     url,
        cookies: extra_cookies
    ) do
        watir.goto url

        @javascript.wait_till_ready
        wait_for_timers

        wait_for_pending_requests

        javascript.set_element_ids
    end

    if @add_request_transitions
        @transitions << transition
    end

    @add_request_transitions = pre_add_request_transitions

    HTTP::Client.update_cookies cookies

    # Capture the page at its initial state.
    capture_snapshot if take_snapshot

    transition
end

#load(resource, options = {}) ⇒ Browser

Returns ‘self`.

Parameters:

  • resource (String, HTTP::Response, Page)

    Loads the given resource in the browser. If it is a string it will be treated like a URL.

Returns:



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/arachni/browser.rb', line 207

def load( resource, options = {} )
    @last_dom_url = nil

    case resource
        when String
            goto resource, options

        when HTTP::Response
            goto preload( resource ), options

        when Page
            HTTP::Client.update_cookies resource.cookie_jar

            @transitions = resource.dom.transitions.dup
            update_skip_states resource.dom.skip_states

            @last_dom_url = resource.dom.url

            @add_request_transitions = false if @transitions.any?
            resource.dom.restore self
            @add_request_transitions = true

        else
            fail Error::Load,
                 "Can't load resource of type #{resource.class}."
    end

    self
end

#load_delayObject



833
834
835
836
# File 'lib/arachni/browser.rb', line 833

def load_delay
    #(intervals + timeouts).map { |t| t[1] }.max
    @javascript.timeouts.compact.map { |t| t[1].to_i }.max
end

#on_fire_event(&block) ⇒ Object



30
# File 'lib/arachni/browser.rb', line 30

advertise :on_fire_event

#on_new_page(&block) ⇒ Object



33
# File 'lib/arachni/browser.rb', line 33

advertise :on_new_page

#on_new_page_with_sink(&block) ⇒ Object



36
# File 'lib/arachni/browser.rb', line 36

advertise :on_new_page_with_sink

#on_response(&block) ⇒ Object



39
# File 'lib/arachni/browser.rb', line 39

advertise :on_response

#page_snapshotsArray<Page>

Returns Page snapshots (stored after events have been fired and JS links clicked) with hashes as keys and pages as values.

Returns:

  • (Array<Page>)

    Page snapshots (stored after events have been fired and JS links clicked) with hashes as keys and pages as values.



703
704
705
# File 'lib/arachni/browser.rb', line 703

def page_snapshots
    @page_snapshots.values
end

#preload(resource) ⇒ Object

Note:

The preloaded resource will be removed once used, for a persistent cache use #cache.

Parameters:



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/arachni/browser.rb', line 242

def preload( resource )
    response =  case resource
                    when HTTP::Response
                        resource

                    when Page
                        resource.response

                    else
                        fail Error::Load,
                             "Can't load resource of type #{resource.class}."
                end

    save_response( response ) if !response.url.include?( request_token )

    @preloads[response.url] = response
    response.url
end

#responseObject



849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
# File 'lib/arachni/browser.rb', line 849

def response
    u = watir.url

    return if skip_path?( u )

    begin
        with_timeout Options.http.request_timeout / 1_000 do
            while !(r = get_response(u))
                sleep 0.1
            end

            fail Timeout::Error if r.timed_out?

            return r
        end
    rescue Timeout::Error
        print_debug "Response for '#{u}' never arrived."
    end

    nil
end

#seleniumSelenium::WebDriver::Driver

Returns Selenium driver interface.

Returns:

  • (Selenium::WebDriver::Driver)

    Selenium driver interface.



873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
# File 'lib/arachni/browser.rb', line 873

def selenium
    return @selenium if @selenium

    client = Selenium::WebDriver::Remote::Http::Typhoeus.new
    client.timeout = WATIR_COM_TIMEOUT

    @selenium = Selenium::WebDriver.for(
        :remote,

        # We need to spawn our own PhantomJS process because Selenium's
        # way sometimes gives us zombies.
        url:                  spawn_browser,
        desired_capabilities: capabilities,
        http_client:          client
    )
end

#shutdownObject



339
340
341
342
343
# File 'lib/arachni/browser.rb', line 339

def shutdown
    watir.close if browser_alive?
    kill_process
    @proxy.shutdown
end

#skip_path?(path) ⇒ Boolean

Returns:

  • (Boolean)


845
846
847
# File 'lib/arachni/browser.rb', line 845

def skip_path?( path )
    enforce_scope? && super( path )
end

#snapshot_idString

Returns Snapshot ID used to determine whether or not a page snapshot has already been seen. Uses both elements and their DOM events and possible audit workload to determine the ID, as page snapshots should be retained both when further browser analysis can be performed and when new element audit workload (but possibly without any DOM relevance) is available.

Returns:

  • (String)

    Snapshot ID used to determine whether or not a page snapshot has already been seen. Uses both elements and their DOM events and possible audit workload to determine the ID, as page snapshots should be retained both when further browser analysis can be performed and when new element audit workload (but possibly without any DOM relevance) is available.



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/arachni/browser.rb', line 441

def snapshot_id
    current_url = url

    id = []
    javascript.dom_elements_with_events.each do |element|
        tag_name   = element['tag_name']
        attributes = element['attributes']
        events     = element['events']

        case tag_name
            when 'a'
                href = attributes['href'].to_s

                if !href.empty?
                    if href.start_with?( 'javascript:' )
                        events << [ :click, href ]
                    else
                        absolute = to_absolute( href, current_url )
                        if !skip_path?( absolute )
                            events << [ :click, absolute ]
                        end
                    end
                else
                    events << [ :click, current_url ]
                end

            when 'input', 'textarea', 'select'
                events << [ tag_name.to_sym ]

            when 'form'
                action = attributes['action'].to_s

                if !action.empty?
                    if action.start_with?( 'javascript:' )
                        events << [ :submit, action ]
                    else
                        absolute = to_absolute( action, current_url )
                        if !skip_path?( absolute )
                            events << [ :submit, absolute ]
                        end
                    end
                else
                    events << [ :submit, current_url ]
                end
        end

        next if events.empty?
        id << "#{tag_name}#{attributes}#{events}".hash
    end

    id.sort.to_s
end

#sourceString

Returns HTML code of the evaluated (DOM/JS/AJAX) page.

Returns:

  • (String)

    HTML code of the evaluated (DOM/JS/AJAX) page.



829
830
831
# File 'lib/arachni/browser.rb', line 829

def source
    watir.html
end

#source_with_line_numbersString

Returns Prefixes each source line with a number.

Returns:

  • (String)

    Prefixes each source line with a number.



195
196
197
198
199
# File 'lib/arachni/browser.rb', line 195

def source_with_line_numbers
    source.lines.map.with_index do |line, i|
        "#{i+1} - #{line}"
    end.join
end

#start_captureBrowser

Starts capturing requests and parses them into elements of pages, accessible via #captured_pages.

Returns:

See Also:



673
674
675
676
# File 'lib/arachni/browser.rb', line 673

def start_capture
    @capture = true
    self
end

#stop_captureBrowser

Stops the HTTP::Request capture.

Returns:

See Also:



686
687
688
689
# File 'lib/arachni/browser.rb', line 686

def stop_capture
    @capture = false
    self
end

#to_pagePage

Returns Converts the current browser window to a page.

Returns:

  • (Page)

    Converts the current browser window to a page.



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
# File 'lib/arachni/browser.rb', line 716

def to_page
    return if !(r = response)

    page         = r.to_page
    page.body    = source

    page.dom.url                  = watir.url
    page.dom.digest               = @javascript.dom_digest
    page.dom.execution_flow_sinks = @javascript.execution_flow_sinks
    page.dom.data_flow_sinks      = @javascript.data_flow_sinks
    page.dom.transitions          = @transitions.dup
    page.dom.skip_states          = skip_states.dup

    page
end

#trigger_event(page, element, event) ⇒ Object

Note:

Captures page #page_snapshots.

Triggers ‘event` on the element described by `tag` on `page`.

Parameters:

  • page (Page)

    Page containing the element’s ‘tag`.

  • element (ElementLocator)
  • event (Symbol)

    Event to trigger.



533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/arachni/browser.rb', line 533

def trigger_event( page, element, event )
    event = event.to_sym
    transition = fire_event( element, event )

    if !transition
        print_info "Could not trigger '#{event}' on '#{element}' because" <<
            ' the page has changed, capturing a new snapshot.'
        capture_snapshot

        print_info 'Restoring page.'
        restore page
        return
    end

    capture_snapshot( transition )
    restore page
end

#trigger_eventsBrowser

Triggers all events on all elements (once) and captures page snapshots.

Returns:



499
500
501
502
503
504
505
506
507
508
509
# File 'lib/arachni/browser.rb', line 499

def trigger_events
    root_page = to_page

    each_element_with_events do |locator, events|
        events.each do |name, _|
            distribute_event( root_page, locator, name.to_sym )
        end
    end

    self
end

#urlString

Returns Current URL.

Returns:



347
348
349
# File 'lib/arachni/browser.rb', line 347

def url
    normalize_url watir.url
end

#wait_for_timersObject



838
839
840
841
842
843
# File 'lib/arachni/browser.rb', line 838

def wait_for_timers
    delay = load_delay
    return if !delay

    sleep [Options.http.request_timeout, delay].min / 1000.0
end