spec/arachni/browser_spec.rb in arachni-1.4 vs spec/arachni/browser_spec.rb in arachni-1.5

- old
+ new

@@ -18,11 +18,11 @@ described_class.asset_domains.clear clear_hit_count end let(:subject) { @browser } - let(:ua) { Arachni::Options.http.user_agent } + let(:ua) { described_class::USER_AGENT } def transitions_from_array( transitions ) transitions.map do |t| element, event = t.first.to_a @@ -312,11 +312,11 @@ Arachni::Options.http.request_timeout = 100 time = Time.now subject.wait_for_timers - expect(Time.now - time).to be < 0.2 + expect(Time.now - time).to be < 0.3 end end end describe '#capture_snapshot' do @@ -440,17 +440,29 @@ before :each do subject.load( ajax_url, take_snapshot: false ) end it 'pushes it to the existing transitions' do - transition = { stuff: :here } - captured = subject.capture_snapshot( stuff: :here ) + transition = Arachni::Page::DOM::Transition.new( + :page, :load + ) + captured = subject.capture_snapshot( transition ) expect(captured.first.dom.transitions).to include transition end end + context 'when a page has the same transitions but different states' do + it 'only captures the first state' do + subject.load( "#{@url}/ever-changing-dom", take_snapshot: false ) + expect(subject.capture_snapshot).to be_any + + subject.load( "#{@url}/ever-changing-dom", take_snapshot: false ) + expect(subject.capture_snapshot).to be_empty + end + end + context 'when there are multiple windows open' do it 'captures snapshots from all windows' do url = "#{@url}open-new-window" @@ -586,24 +598,10 @@ expect(response).to be_kind_of Arachni::HTTP::Response expect(response.url).to eq(@url) end end - context 'when a response is cached' do - it 'is passed each response' do - responses = [] - @browser.on_response { |response| responses << response } - - @browser.cache Arachni::HTTP::Client.get( @url, mode: :sync ) - @browser.goto @url - - response = responses.first - expect(response).to be_kind_of Arachni::HTTP::Response - expect(response.url).to eq(@url) - end - end - context 'when a request is performed by the browser' do it 'is passed each response' do responses = [] @browser.on_response { |response| responses << response } @@ -653,11 +651,10 @@ attributes: { 'href' => 'javascript:level3();' } } => :click }, - { "#{@url}level4" => :request }, { "#{@url}level4" => :request } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, @@ -691,11 +688,10 @@ 'href' => 'javascript:level3();' } } => :click }, { "#{@url}level4" => :request }, - { "#{@url}level4" => :request }, { { tag_name: 'div', attributes: { 'onclick' => 'level6();', @@ -743,11 +739,10 @@ attributes: { 'href' => 'javascript:level3();' } } => :click }, - { "#{@url}level4" => :request }, { "#{@url}level4" => :request } ] ].map { |transitions| transitions_from_array( transitions ) }) end end @@ -779,20 +774,19 @@ expect(doms[0].execution_flow_sinks.size).to eq(2) entry = doms[0].execution_flow_sinks[0] expect(entry.data).to eq([1]) - expect(entry.trace.size).to eq(3) expect(entry.trace[0].function.name).to eq('onClick') expect(entry.trace[0].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_execution_flow_sink(1)' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_execution_flow_sink(1)' expect(entry.trace[0].function.arguments).to eq([1, 2]) expect(entry.trace[1].function.name).to eq('onClick2') expect(entry.trace[1].function.source).to start_with 'function onClick2' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick' expect(entry.trace[1].function.arguments).to eq(%w(blah1 blah2 blah3)) expect(entry.trace[2].function.name).to eq('onmouseover') expect(entry.trace[2].function.source).to start_with 'function onmouseover' @@ -803,25 +797,24 @@ expect(event['srcElement']).to eq(link) expect(event['type']).to eq('mouseover') entry = doms[0].execution_flow_sinks[1] expect(entry.data).to eq([1]) - expect(entry.trace.size).to eq(4) expect(entry.trace[0].function.name).to eq('onClick3') expect(entry.trace[0].function.source).to start_with 'function onClick3' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_execution_flow_sink(1)' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_execution_flow_sink(1)' expect(entry.trace[0].function.arguments).to be_empty expect(entry.trace[1].function.name).to eq('onClick') expect(entry.trace[1].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick3' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick3' expect(entry.trace[1].function.arguments).to eq([1, 2]) expect(entry.trace[2].function.name).to eq('onClick2') expect(entry.trace[2].function.source).to start_with 'function onClick2' - expect(@browser.source.split("\n")[entry.trace[2].line]).to include 'onClick' + expect(@browser.source.split("\n")[entry.trace[2].line - 1]).to include 'onClick' expect(entry.trace[2].function.arguments).to eq(%w(blah1 blah2 blah3)) expect(entry.trace[3].function.name).to eq('onmouseover') expect(entry.trace[3].function.source).to start_with 'function onmouseover' @@ -848,45 +841,43 @@ expect(doms[1].execution_flow_sinks.size).to eq(2) entry = doms[1].execution_flow_sinks[0] expect(entry.data).to eq([1]) - expect(entry.trace.size).to eq(2) expect(entry.trace[0].function.name).to eq('onClick') expect(entry.trace[0].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_execution_flow_sink(1)' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_execution_flow_sink(1)' expect(entry.trace[0].function.arguments).to eq(%w(some-arg arguments-arg here-arg)) expect(entry.trace[1].function.name).to eq('onsubmit') expect(entry.trace[1].function.source).to start_with 'function onsubmit' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick' event = entry.trace[1].function.arguments.first form = "<form id=\"my_form\" onsubmit=\"onClick('some-arg', 'arguments-arg', 'here-arg'); return false;\">\n </form>" expect(event['target']).to eq(form) expect(event['srcElement']).to eq(form) expect(event['type']).to eq('submit') entry = doms[1].execution_flow_sinks[1] expect(entry.data).to eq([1]) - expect(entry.trace.size).to eq(3) expect(entry.trace[0].function.name).to eq('onClick3') expect(entry.trace[0].function.source).to start_with 'function onClick3' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_execution_flow_sink(1)' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_execution_flow_sink(1)' expect(entry.trace[0].function.arguments).to be_empty expect(entry.trace[1].function.name).to eq('onClick') expect(entry.trace[1].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick3()' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick3()' expect(entry.trace[1].function.arguments).to eq(%w(some-arg arguments-arg here-arg)) expect(entry.trace[2].function.name).to eq('onsubmit') expect(entry.trace[2].function.source).to start_with 'function onsubmit' - expect(@browser.source.split("\n")[entry.trace[2].line]).to include 'onClick(' + expect(@browser.source.split("\n")[entry.trace[2].line - 1]).to include 'onClick(' event = entry.trace[2].function.arguments.first form = "<form id=\"my_form\" onsubmit=\"onClick('some-arg', 'arguments-arg', 'here-arg'); return false;\">\n </form>" expect(event['target']).to eq(form) @@ -906,20 +897,19 @@ expect(doms[0].data_flow_sinks.size).to eq(2) entry = doms[0].data_flow_sinks[0] expect(entry.function).to eq('blah') - expect(entry.trace.size).to eq(3) expect(entry.trace[0].function.name).to eq('onClick') expect(entry.trace[0].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_data_flow_sink(' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_data_flow_sink(' expect(entry.trace[0].function.arguments).to eq([1, 2]) expect(entry.trace[1].function.name).to eq('onClick2') expect(entry.trace[1].function.source).to start_with 'function onClick2' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick' expect(entry.trace[1].function.arguments).to eq(%w(blah1 blah2 blah3)) expect(entry.trace[2].function.name).to eq('onmouseover') expect(entry.trace[2].function.source).to start_with 'function onmouseover' @@ -930,25 +920,24 @@ expect(event['srcElement']).to eq(link) expect(event['type']).to eq('mouseover') entry = doms[0].data_flow_sinks[1] expect(entry.function).to eq('blah') - expect(entry.trace.size).to eq(4) expect(entry.trace[0].function.name).to eq('onClick3') expect(entry.trace[0].function.source).to start_with 'function onClick3' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_data_flow_sink(' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_data_flow_sink(' expect(entry.trace[0].function.arguments).to be_empty expect(entry.trace[1].function.name).to eq('onClick') expect(entry.trace[1].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick3' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick3' expect(entry.trace[1].function.arguments).to eq([1, 2]) expect(entry.trace[2].function.name).to eq('onClick2') expect(entry.trace[2].function.source).to start_with 'function onClick2' - expect(@browser.source.split("\n")[entry.trace[2].line]).to include 'onClick' + expect(@browser.source.split("\n")[entry.trace[2].line - 1]).to include 'onClick' expect(entry.trace[2].function.arguments).to eq(%w(blah1 blah2 blah3)) expect(entry.trace[3].function.name).to eq('onmouseover') expect(entry.trace[3].function.source).to start_with 'function onmouseover' @@ -961,45 +950,43 @@ expect(doms[1].data_flow_sinks.size).to eq(2) entry = doms[1].data_flow_sinks[0] expect(entry.function).to eq('blah') - expect(entry.trace.size).to eq(2) expect(entry.trace[0].function.name).to eq('onClick') expect(entry.trace[0].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_data_flow_sink(' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_data_flow_sink(' expect(entry.trace[0].function.arguments).to eq(%w(some-arg arguments-arg here-arg)) expect(entry.trace[1].function.name).to eq('onsubmit') expect(entry.trace[1].function.source).to start_with 'function onsubmit' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick' event = entry.trace[1].function.arguments.first form = "<form id=\"my_form\" onsubmit=\"onClick('some-arg', 'arguments-arg', 'here-arg'); return false;\">\n </form>" expect(event['target']).to eq(form) expect(event['srcElement']).to eq(form) expect(event['type']).to eq('submit') entry = doms[1].data_flow_sinks[1] expect(entry.function).to eq('blah') - expect(entry.trace.size).to eq(3) expect(entry.trace[0].function.name).to eq('onClick3') expect(entry.trace[0].function.source).to start_with 'function onClick3' - expect(@browser.source.split("\n")[entry.trace[0].line]).to include 'log_data_flow_sink(' + expect(@browser.source.split("\n")[entry.trace[0].line - 1]).to include 'log_data_flow_sink(' expect(entry.trace[0].function.arguments).to be_empty expect(entry.trace[1].function.name).to eq('onClick') expect(entry.trace[1].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[entry.trace[1].line]).to include 'onClick3()' + expect(@browser.source.split("\n")[entry.trace[1].line - 1]).to include 'onClick3()' expect(entry.trace[1].function.arguments).to eq(%w(some-arg arguments-arg here-arg)) expect(entry.trace[2].function.name).to eq('onsubmit') expect(entry.trace[2].function.source).to start_with 'function onsubmit' - expect(@browser.source.split("\n")[entry.trace[2].line]).to include 'onClick(' + expect(@browser.source.split("\n")[entry.trace[2].line - 1]).to include 'onClick(' event = entry.trace[2].function.arguments.first form = "<form id=\"my_form\" onsubmit=\"onClick('some-arg', 'arguments-arg', 'here-arg'); return false;\">\n </form>" expect(event['target']).to eq(form) @@ -1095,12 +1082,10 @@ end describe '#to_page' do it "converts the working window to an #{Arachni::Page}" do - ua = Arachni::Options.http.user_agent - @browser.load( @url ) page = @browser.to_page expect(page).to be_kind_of Arachni::Page @@ -1109,18 +1094,20 @@ expect(page.body).to include( ua ) end it "assigns the proper #{Arachni::Page::DOM}#digest" do @browser.load( @url ) - expect(@browser.to_page.dom.instance_variable_get(:@digest)).to eq( - '<HTML><HEAD><SCRIPT src=http://' << - 'javascript.browser.arachni/polyfills.js><SCRIPT src=http://' << - 'javascript.browser.arachni/' << - 'taint_tracer.js><SCRIPT src=http://javascript.' << - 'browser.arachni/dom_monitor.js><SCRIPT><TITLE><BODY><' << - 'DIV><SCRIPT type=text/javascript><SCRIPT type=text/javascript>' - ) + expect(@browser.to_page.dom.digest).to eq(32000153) + + # expect(@browser.to_page.dom.instance_variable_get(:@digest)).to eq( + # '<HTML><HEAD><SCRIPT src=http://' << + # 'javascript.browser.arachni/polyfills.js><SCRIPT src=http://' << + # 'javascript.browser.arachni/' << + # 'taint_tracer.js><SCRIPT src=http://javascript.' << + # 'browser.arachni/dom_monitor.js><SCRIPT><TITLE><BODY><' << + # 'DIV><SCRIPT type=text/javascript><SCRIPT type=text/javascript>' + # ) end it "assigns the proper #{Arachni::Page::DOM}#transitions" do @browser.load( @url ) page = @browser.to_page @@ -1138,10 +1125,16 @@ page = pages.last expect(page.dom.skip_states).to be_subset @browser.skip_states end + it "assigns the proper #{Arachni::Page::DOM}#cookies" do + @browser.load "#{@url}/dom-cookies-names" + + expect(@browser.to_page.dom.cookies).to eq @browser.cookies + end + it "assigns the proper #{Arachni::Page::DOM} sink data" do @browser.load "#{web_server_url_for( :taint_tracer )}/debug" << "?input=#{@browser.javascript.log_execution_flow_sink_stub(1)}" @browser.watir.form.submit @@ -1150,20 +1143,19 @@ first_entry = sink_data.first expect(sink_data).to eq([first_entry]) expect(first_entry.data).to eq([1]) - expect(first_entry.trace.size).to eq(2) expect(first_entry.trace[0].function.name).to eq('onClick') expect(first_entry.trace[0].function.source).to start_with 'function onClick' - expect(@browser.source.split("\n")[first_entry.trace[0].line]).to include 'log_execution_flow_sink(1)' + expect(@browser.source.split("\n")[first_entry.trace[0].line - 1]).to include 'log_execution_flow_sink(1)' expect(first_entry.trace[0].function.arguments).to eq(%w(some-arg arguments-arg here-arg)) expect(first_entry.trace[1].function.name).to eq('onsubmit') expect(first_entry.trace[1].function.source).to start_with 'function onsubmit' - expect(@browser.source.split("\n")[first_entry.trace[1].line]).to include 'onClick(' + expect(@browser.source.split("\n")[first_entry.trace[1].line - 1]).to include 'onClick(' expect(first_entry.trace[1].function.arguments.size).to eq(1) event = first_entry.trace[1].function.arguments.first form = "<form id=\"my_form\" onsubmit=\"onClick('some-arg', 'arguments-arg', 'here-arg'); return false;\">\n </form>" @@ -1250,11 +1242,11 @@ input = @browser.to_page.ui_inputs.first expect(input.action).to eq @browser.url expect(input.source).to eq '<input oninput="handleOnInput();" id="my-input" name="my-input" value="1">' - expect(input.method).to eq :oninput + expect(input.method).to eq :input end end context 'without DOM events' do it 'ignores it' do @@ -1271,11 +1263,11 @@ input = @browser.to_page.ui_inputs.first expect(input.action).to eq @browser.url expect(input.source).to eq '<textarea oninput="handleOnInput();" id="my-input" name="my-input">' - expect(input.method).to eq :oninput + expect(input.method).to eq :input end end context 'without DOM events' do it 'ignores it' do @@ -1404,10 +1396,28 @@ it 'does not set #skip_dom' do expect(cookies.find { |c| c.name == 'js_cookie3' }.skip_dom).to be_truthy end end end + + context 'when taints are not exact matches' do + context 'names' do + let(:page) { 'dom-cookies-names-substring' } + + it 'does not set #skip_dom' do + expect(cookies.find { |c| c.name == 'js_cookie3' }.skip_dom).to be_truthy + end + end + + context 'values' do + let(:page) { 'dom-cookies-values-substring' } + + it 'does not set #skip_dom' do + expect(cookies.find { |c| c.name == 'js_cookie3' }.skip_dom).to be_truthy + end + end + end end context 'false' do before do Arachni::Options.audit.skip_elements :cookies @@ -1459,11 +1469,11 @@ end it 'accepts events without the "on" prefix' do pages_should_not_have_form_with_input [@browser.to_page], 'by-ajax' - @browser.fire_event @browser.selenium.find_element( id: 'my-div' ), :onclick + @browser.fire_event @browser.selenium.find_element( id: 'my-div' ), :click pages_should_have_form_with_input [@browser.to_page], 'by-ajax' @browser.fire_event @browser.selenium.find_element( id: 'my-div' ), :click pages_should_have_form_with_input [@browser.to_page], 'by-ajax' end @@ -1477,10 +1487,22 @@ transition.play @browser pages_should_have_form_with_input [@browser.to_page], 'by-ajax' end + context 'when new elements are introduced' do + let(:url) { "#{@url}/trigger_events/with_new_elements" } + + it 'sets element IDs' do + expect(@browser.selenium.find_elements( :css, 'a' )).to be_empty + + @browser.fire_event @browser.selenium.find_element( id: 'my-div' ), :click + + expect(@browser.selenium.find_elements( :css, 'a' ).first.opening_tag).to eq '<a href="#blah" data-arachni-id="2073105">' + end + end + context 'when new timers are introduced' do let(:url) { "#{@url}/trigger_events/with_new_timers/3000" } it 'waits for them' do @browser.fire_event @browser.selenium.find_element( id: 'my-div' ), :click @@ -1558,17 +1580,79 @@ context 'form' do context ':submit' do let(:url) { "#{@url}/fire_event/form/onsubmit" } - context 'when option' do - describe ':inputs' do + def element + @browser.selenium.find_element(:tag_name, :form) + end - def element - @browser.selenium.find_element(:tag_name, :form) - end + context 'when there is a submit button' do + let(:url) { "#{@url}/fire_event/form/submit_button" } + let(:inputs) do + { + name: 'The Dude', + email: 'the.dude@abides.com' + } + end + it 'clicks it' do + @browser.fire_event element, :submit, inputs: inputs + + expect(@browser.watir.div( id: 'container-name' ).text).to eq( + inputs[:name] + ) + expect(@browser.watir.div( id: 'container-email' ).text).to eq( + inputs[:email] + ) + end + end + + context 'when there is a submit input' do + let(:url) { "#{@url}/fire_event/form/submit_input" } + let(:inputs) do + { + name: 'The Dude', + email: 'the.dude@abides.com' + } + end + + it 'clicks it' do + @browser.fire_event element, :submit, inputs: inputs + + expect(@browser.watir.div( id: 'container-name' ).text).to eq( + inputs[:name] + ) + expect(@browser.watir.div( id: 'container-email' ).text).to eq( + inputs[:email] + ) + end + end + + context 'when there is no submit button or input' do + let(:url) { "#{@url}/fire_event/form/onsubmit" } + let(:inputs) do + { + name: 'The Dude', + email: 'the.dude@abides.com' + } + end + + it 'triggers the submit event' do + @browser.fire_event element, :submit, inputs: inputs + + expect(@browser.watir.div( id: 'container-name' ).text).to eq( + inputs[:name] + ) + expect(@browser.watir.div( id: 'container-email' ).text).to eq( + inputs[:email] + ) + end + end + + context 'when option' do + describe ':inputs' do context 'is given' do let(:inputs) do { name: 'The Dude', email: 'the.dude@abides.com' @@ -1758,10 +1842,63 @@ end end end end + context ':fill' do + before(:each) do + @browser.load url + end + + let(:url) { "#{@url}/fire_event/form/onsubmit" } + let(:inputs) do + { + name: "The Dude", + email: "the.dude@abides.com" + } + end + + def element + @browser.selenium.find_element(:tag_name, :form) + end + + it 'fills in the form inputs' do + @browser.fire_event element, :fill, inputs: inputs + + expect(@browser.watir.textarea( name: 'name' ).value).to eq( + inputs[:name] + ) + + expect(@browser.watir.input( id: 'email' ).value).to eq( + inputs[:email] + ) + + expect(@browser.watir.div( id: 'container-name' ).text).to be_empty + expect(@browser.watir.div( id: 'container-email' ).text).to be_empty + end + + it 'returns a playable transition' do + @browser.load url + transition = @browser.fire_event element, :fill, inputs: inputs + + @browser.load url + + expect(@browser.watir.textarea( name: 'name' ).value).to be_empty + expect(@browser.watir.input( id: 'email' ).value).to be_empty + + transition.play @browser + + expect(@browser.watir.textarea( name: 'name' ).value).to eq( + inputs[:name] + ) + + expect(@browser.watir.input( id: 'email' ).value).to eq( + inputs[:email] + ) + end + end + context 'image button' do context ':click' do before( :each ) { @browser.start_capture } let(:url) { "#{@url}fire_event/form/image-input" } @@ -1798,11 +1935,20 @@ end end end context 'input' do - described_class::Javascript::EVENTS_PER_ELEMENT[:input].each do |event| + [ + :onselect, + :onchange, + :onfocus, + :onblur, + :onkeydown, + :onkeypress, + :onkeyup, + :oninput + ].each do |event| calculate_expectation = proc do |string| [:onkeypress, :onkeydown].include?( event ) ? string[0...-1] : string end @@ -1892,48 +2038,10 @@ end end end end - describe '#elements_with_events' do - before :each do - @browser.load url - end - - let(:elements_with_events) do - elements_with_events = {} - @browser.each_element_with_events do |locator, events| - elements_with_events[locator] = events - end - elements_with_events - end - - let(:url) { @url + '/trigger_events' } - - it 'returns all elements with associated events' do - expect(subject.elements_with_events.to_s).to eq elements_with_events.to_s - end - - it 'caches results' do - expect(subject).to receive(:each_element_with_events) - subject.elements_with_events - - expect(subject).to_not receive(:each_element_with_events) - subject.elements_with_events - end - - context 'when passed true' do - it 'clears the cache' do - expect(subject).to receive(:each_element_with_events) - subject.elements_with_events - - expect(subject).to receive(:each_element_with_events) - subject.elements_with_events( true ) - end - end - end - describe '#each_element_with_events' do before :each do @browser.load url end let(:elements_with_events) do @@ -1950,18 +2058,18 @@ [ described_class::ElementLocator.new( tag_name: 'body', attributes: { 'onmouseover' => 'makePOST();' } ), - { onmouseover: ['makePOST();'] } + { mouseover: ['makePOST();'] } ], [ described_class::ElementLocator.new( tag_name: 'div', attributes: { 'id' => 'my-div', 'onclick' => 'addForm();' } ), - { onclick: ['addForm();']} + { click: ['addForm();']} ] ]) end context ':a' do @@ -2072,11 +2180,11 @@ rescue end end locators.each do |element| - @browser.javascript.class.events.each do |e| + described_class::Javascript::EVENTS.each do |e| begin @browser.trigger_event @browser.to_page, element, e rescue next end @@ -2087,10 +2195,11 @@ pages_should_have_form_with_input @browser.captured_pages, 'ajax-token' end end describe '#trigger_events' do + it 'returns self' do expect(@browser.load( @url + '/explore' ).trigger_events).to eq(@browser) end it 'waits for AJAX requests to complete' do @@ -2140,11 +2249,10 @@ attributes: { 'href' => 'javascript:inHref();' } } => :click }, - { "#{@url}href-ajax" => :request }, { "#{@url}post-ajax" => :request }, { "#{@url}href-ajax" => :request } ] ].map { |transitions| transitions_from_array( transitions ) }) end @@ -2171,19 +2279,37 @@ @browser.load( "#{@url}form-with-image-button" ).start_capture.trigger_events pages_should_have_form_with_input @browser.captured_pages, 'myImageButton.x' pages_should_have_form_with_input @browser.captured_pages, 'myImageButton.y' end end + + context 'when OptionGroups::Scope#dom_event_limit' do + context 'has been set' do + it 'only triggers that amount of events' do + Arachni::Options.scope.dom_event_limit = 1 + + @browser.load( "#{@url}form-with-image-button" ).start_capture.trigger_events + + expect(@browser.flush_pages.size).to eq 1 + end + end + + context 'has not been set' do + it 'triggers all events' do + Arachni::Options.scope.dom_event_limit = nil + + @browser.load( "#{@url}form-with-image-button" ).start_capture.trigger_events + + expect(@browser.flush_pages.size).to eq 2 + end + end + end end describe '#source' do it 'returns the evaluated HTML source' do @browser.load @url - - ua = Arachni::Options.http.user_agent - expect(ua).not_to be_empty - expect(@browser.source).to include( ua ) end end describe '#watir' do @@ -2199,36 +2325,60 @@ end describe '#goto' do it 'loads the given URL' do @browser.goto @url - - ua = Arachni::Options.http.user_agent - expect(ua).not_to be_empty - expect(@browser.source).to include( ua ) end it 'returns a playable transition' do transition = @browser.goto( @url ) @browser.shutdown @browser = described_class.new transition.play( @browser ) - ua = Arachni::Options.http.user_agent - expect(ua).not_to be_empty expect(@browser.source).to include( ua ) end it 'puts the domain in the asset domains list' do subject.goto @url expect(described_class.asset_domains).to include Arachni::URI( @url ).domain end + it 'does not receive a Content-Security-Policy header' do + subject.goto "#{@url}/Content-Security-Policy" + expect(subject.response.code).to eq(200) + expect(subject.response.headers).not_to include 'Content-Security-Policy' + end + context 'when requesting the page URL' do + it 'does not receive a Date header' do + subject.goto "#{@url}/Date" + expect(subject.response.code).to eq(200) + expect(subject.response.headers).not_to include 'Date' + end + + it 'does not receive an Etag header' do + subject.goto "#{@url}/Etag" + expect(subject.response.code).to eq(200) + expect(subject.response.headers).not_to include 'Etag' + end + + it 'does not receive a Cache-Control header' do + subject.goto "#{@url}/Cache-Control" + expect(subject.response.code).to eq(200) + expect(subject.response.headers).not_to include 'Cache-Control' + end + + it 'does not receive a Last-Modified header' do + subject.goto "#{@url}/Last-Modified" + expect(subject.response.code).to eq(200) + expect(subject.response.headers).not_to include 'Last-Modified' + end + it 'does not send If-None-Match request headers' do subject.goto "#{@url}/If-None-Match" expect(subject.response.code).to eq(200) expect(subject.response.request.headers).not_to include 'If-None-Match' @@ -2247,10 +2397,70 @@ expect(subject.response.request.headers).not_to include 'If-Modified-Since' end end context 'when requesting something other than the page URL' do + it 'receives a Date header' do + url = "#{@url}Date" + + response = nil + subject.on_response do |r| + next if r.url == url + response = r + end + + subject.goto url + + expect(response.code).to eq(200) + expect(response.headers).to include 'Date' + end + + it 'receives an Etag header' do + url = "#{@url}Etag" + + response = nil + subject.on_response do |r| + next if r.url == url + response = r + end + + subject.goto url + + expect(response.code).to eq(200) + expect(response.headers).to include 'Etag' + end + + it 'receives a Cache-Control header' do + url = "#{@url}Cache-Control" + + response = nil + subject.on_response do |r| + next if r.url == url + response = r + end + + subject.goto url + + expect(response.code).to eq(200) + expect(response.headers).to include 'Cache-Control' + end + + it 'receives a Last-Modified header' do + url = "#{@url}Last-Modified" + + response = nil + subject.on_response do |r| + next if r.url == url + response = r + end + + subject.goto url + + expect(response.code).to eq(200) + expect(response.headers).to include 'Last-Modified' + end + it 'sends If-None-Match request headers' do url = "#{@url}If-None-Match" response = nil subject.on_response do |r| @@ -2817,82 +3027,10 @@ end end end end - describe '#cache' do - it 'keeps entries after they are used' do - @browser.cache Arachni::HTTP::Client.get( @url, mode: :sync ) - clear_hit_count - - expect(hit_count).to eq(0) - - @browser.load @url - expect(@browser.source).to include( ua ) - expect(@browser.cache).to include( @url ) - - expect(hit_count).to eq(0) - - 2.times do - @browser.load @url - expect(@browser.source).to include( ua ) - end - - expect(@browser.cache).to include( @url ) - - expect(hit_count).to eq(0) - end - - it 'returns the URL of the resource' do - response = Arachni::HTTP::Client.get( @url, mode: :sync ) - expect(@browser.cache( response )).to eq(response.url) - - @browser.load response.url - expect(@browser.source).to include( ua ) - expect(@browser.cache).to include( response.url ) - end - - context 'when given a' do - describe 'Arachni::HTTP::Response' do - it 'caches it' do - @browser.cache Arachni::HTTP::Client.get( @url, mode: :sync ) - clear_hit_count - - expect(hit_count).to eq(0) - - @browser.load @url - expect(@browser.source).to include( ua ) - expect(@browser.cache).to include( @url ) - - expect(hit_count).to eq(0) - end - end - - describe 'Arachni::Page' do - it 'caches it' do - @browser.cache Arachni::Page.from_url( @url ) - clear_hit_count - - expect(hit_count).to eq(0) - - @browser.load @url - expect(@browser.source).to include( ua ) - expect(@browser.cache).to include( @url ) - - expect(hit_count).to eq(0) - end - end - - describe 'other' do - it 'raises Arachni::Browser::Error::Load' do - expect { @browser.cache [] }.to raise_error Arachni::Browser::Error::Load - end - end - - end - end - describe '#start_capture' do before(:each) { @browser.start_capture } it 'parses requests into elements of pages' do @browser.load @url + '/with-ajax' @@ -2946,10 +3084,27 @@ expect(form.method).to eq(:get) end end context 'when a POST request is performed' do + context 'with query parameters' do + it "is added as an #{Arachni::Element::Form} to the page" do + @browser.load @url + '/with-ajax' + + pages = @browser.captured_pages + expect(pages.size).to eq(2) + + form = find_page_with_form_with_input( pages, 'post-name' ). + forms.find { |form| form.inputs.include? 'post-query' } + + expect(form.url).to eq(@url + 'with-ajax') + expect(form.action).to eq(@url + 'post-ajax') + expect(form.inputs).to eq({ 'post-query' => 'blah' }) + expect(form.method).to eq(:get) + end + end + context 'with form data' do it "is added as an #{Arachni::Element::Form} to the page" do @browser.load @url + '/with-ajax' pages = @browser.captured_pages @@ -2957,11 +3112,11 @@ form = find_page_with_form_with_input( pages, 'post-name' ). forms.find { |form| form.inputs.include? 'post-name' } expect(form.url).to eq(@url + 'with-ajax') - expect(form.action).to eq(@url + 'post-ajax') + expect(form.action).to eq(@url + 'post-ajax?post-query=blah') expect(form.inputs).to eq({ 'post-name' => 'post-value' }) expect(form.method).to eq(:post) end end @@ -3072,11 +3227,11 @@ cookies = @browser.cookies cookie = cookies.find { |c| c.name == 'include_subdomains' } expect(cookie.name).to eq 'include_subdomains' expect(cookie.value).to eq 'bar1' - expect(cookie.domain).to eq '.127.0.0.2' + expect(cookie.domain).to eq ".#{Arachni::URI( @url ).host}" end it 'ignores cookies for other domains' do @browser.load "#{@url}/cookies/domains" @@ -3124,124 +3279,9 @@ end context 'when no page is available' do it 'returns an empty Array' do expect(@browser.cookies).to be_empty - end - end - end - - describe '#snapshot_id' do - before(:each) do - Arachni::Options.url = @url - - @empty_snapshot_id ||= @browser.load( empty_snapshot_id_url ).snapshot_id - - @snapshot_id = @browser.load( url ).snapshot_id - end - - let(:empty_snapshot_id_url) { @url + '/snapshot_id/default' } - let(:empty_snapshot_id) do - @empty_snapshot_id - end - let(:snapshot_id) do - @snapshot_id - end - - let(:url) { @url + '/trigger_events' } - - it 'returns a DOM digest' do - expect(snapshot_id).to eq(@browser.load( url ).snapshot_id) - end - - context 'when there are new cookies' do - let(:url) { @url + '/each_element_with_events/set-cookie' } - - it 'takes them into account' do - @browser.fire_event described_class::ElementLocator.new( - tag_name: :button, - attributes: { - onclick: 'setCookie()' - } - ), :click - - expect(@browser.snapshot_id).not_to eq(snapshot_id) - end - end - - context ':a' do - context 'and the href is not empty' do - context 'and it starts with javascript:' do - let(:url) { @url + '/each_element_with_events/a/href/javascript' } - - it 'takes it into account' do - expect(snapshot_id).not_to eq(empty_snapshot_id) - end - end - - context 'and it does not start with javascript:' do - let(:url) { @url + '/each_element_with_events/a/href/regular' } - - it 'takes it into account' do - expect(snapshot_id).not_to eq(empty_snapshot_id) - end - end - - context 'and is out of scope' do - let(:url) { @url + '/each_element_with_events/a/href/out-of-scope' } - - it 'is ignored' do - expect(snapshot_id).to eq(empty_snapshot_id) - end - end - end - - context 'and the href is empty' do - let(:url) { @url + '/each_element_with_events/a/href/empty' } - - it 'takes it into account' do - expect(snapshot_id).not_to eq(empty_snapshot_id) - end - end - end - - context ':form' do - let(:empty_snapshot_id_url) { @url + '/snapshot_id/form/default' } - - context ':input' do - context 'of type "image"' do - let(:url) { @url + '/each_element_with_events/form/input/image' } - - it 'takes it into account' do - expect(snapshot_id).not_to eq(empty_snapshot_id) - end - end - end - - context 'and the action is not empty' do - context 'and it starts with javascript:' do - let(:url) { @url + '/each_element_with_events/form/action/javascript' } - - it 'takes it into account' do - expect(snapshot_id).not_to eq(empty_snapshot_id) - end - end - - context 'and it does not start with javascript:' do - let(:url) { @url + '/each_element_with_events/form/action/regular' } - - it 'takes it into account' do - expect(snapshot_id).not_to eq(empty_snapshot_id) - end - end - - context 'and is out of scope' do - let(:url) { @url + '/each_element_with_events/form/action/out-of-scope' } - - it 'is ignored' do - expect(snapshot_id).to eq(empty_snapshot_id) - end - end end end end end