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?, #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.



125
126
127
128
129
130
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
# File 'lib/arachni/browser.rb', line 125

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:



92
93
94
# File 'lib/arachni/browser.rb', line 92

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.



89
90
91
# File 'lib/arachni/browser.rb', line 89

def page_snapshots_with_sinks
  @page_snapshots_with_sinks
end

#pidInteger (readonly)

Returns:

  • (Integer)


102
103
104
# File 'lib/arachni/browser.rb', line 102

def pid
  @pid
end

#preloadsHash (readonly)

Returns Preloaded resources, by URL.

Returns:

  • (Hash)

    Preloaded resources, by URL.



74
75
76
# File 'lib/arachni/browser.rb', line 74

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?


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

def skip_states
  @skip_states
end

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



70
71
72
# File 'lib/arachni/browser.rb', line 70

def transitions
  @transitions
end

#watirWatir::Browser (readonly)

Returns Watir driver interface.

Returns:

  • (Watir::Browser)

    Watir driver interface.



78
79
80
# File 'lib/arachni/browser.rb', line 78

def watir
  @watir
end

Class Method Details

.executableString

Returns Path to the PhantomJS executable.

Returns:

  • (String)

    Path to the PhantomJS executable.



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

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.



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

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.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/arachni/browser.rb', line 257

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:



690
691
692
# File 'lib/arachni/browser.rb', line 690

def capture?
    !!@capture
end

#capture_snapshot(transition = nil) ⇒ Object



726
727
728
729
730
731
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
# File 'lib/arachni/browser.rb', line 726

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.



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

def captured_pages
    @captured_pages
end

#cookiesArray<Cookie>

Returns Browser cookies.

Returns:



799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# File 'lib/arachni/browser.rb', line 799

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 )
    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:



514
515
516
# File 'lib/arachni/browser.rb', line 514

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.



381
382
383
384
385
386
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
# File 'lib/arachni/browser.rb', line 381

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.



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/arachni/browser.rb', line 353

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:



557
558
559
560
561
562
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
# File 'lib/arachni/browser.rb', line 557

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:



775
776
777
778
779
# File 'lib/arachni/browser.rb', line 775

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:



790
791
792
793
794
795
# File 'lib/arachni/browser.rb', line 790

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:



287
288
289
290
291
292
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
# File 'lib/arachni/browser.rb', line 287

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:



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/arachni/browser.rb', line 201

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



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

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.



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

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:



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/arachni/browser.rb', line 236

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



843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
# File 'lib/arachni/browser.rb', line 843

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.



867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
# File 'lib/arachni/browser.rb', line 867

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



333
334
335
336
337
# File 'lib/arachni/browser.rb', line 333

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

#skip_path?(path) ⇒ Boolean

Returns:

  • (Boolean)


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

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.



435
436
437
438
439
440
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
# File 'lib/arachni/browser.rb', line 435

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.



823
824
825
# File 'lib/arachni/browser.rb', line 823

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.



189
190
191
192
193
# File 'lib/arachni/browser.rb', line 189

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:



667
668
669
670
# File 'lib/arachni/browser.rb', line 667

def start_capture
    @capture = true
    self
end

#stop_captureBrowser

Stops the HTTP::Request capture.

Returns:

See Also:



680
681
682
683
# File 'lib/arachni/browser.rb', line 680

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.



710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/arachni/browser.rb', line 710

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.



527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'lib/arachni/browser.rb', line 527

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:



493
494
495
496
497
498
499
500
501
502
503
# File 'lib/arachni/browser.rb', line 493

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:



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

def url
    normalize_url watir.url
end

#wait_for_timersObject



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

def wait_for_timers
    delay = load_delay
    return if !delay

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